【问题标题】:Using SFINAE to detect method with GCC使用 SFINAE 检测 GCC 方法
【发布时间】:2020-08-04 18:47:21
【问题描述】:

我在使用 SFINAE 概念来检测类是否具有某些编译器(最相关的是 GCC)的特定模板方法时遇到了麻烦。考虑下面的代码。它可以按预期与 MSVC 一起编译和工作;但是,当我使用 GCC 编译相同的代码时,它会抱怨 std::vector<double> 没有序列化方法。当然,该方法不存在的事实是正确的,但我预计这会导致替换失败,并且编译器会确定其他不太专业的回退是最合适的。我是否遗漏了有关 SFINAE 的某些内容,或者这是 GCC 的错误?

#include <iostream>
#include <utility>
#include <vector>

class SerializerBase
{
  public:
    SerializerBase() {}
};

template<typename T>
struct has_serialize
{
    template<typename C>
    static constexpr auto test(...) -> std::false_type;
    /// Note: this is where the compiler complains about the .serialize method
    /// during substitution.
    template<typename C>
    static constexpr auto test(int)
      -> decltype(std::declval<T>().serialize(std::declval<SerializerBase&>()), std::true_type());

    using result_type       = decltype(test<T>(0));
    static const bool value = result_type::value;
};

class Serializer : public SerializerBase
{
  public:
    Serializer() {}

    template<typename T,
             typename std::enable_if<!has_serialize<T>::value, T>::type* = nullptr>
    void operator()(const T& v)
    {
        std::cout << "fallback called" << std::endl;
    }

    template<typename T,
             typename std::enable_if<has_serialize<T>::value, T>::type* = nullptr>
    void operator()(const T& v)
    {
        v.serialize(*this);
    }
};

struct Foo
{
    template<typename SerializerType>
    void serialize(SerializerType& s) const
    {
        std::cout << "serialize called" << std::endl;
    }
};

int main()
{
    Serializer s;
    std::vector<double> v;
    Foo f;
    s(v);
    s(f);

    return 0;
}

【问题讨论】:

  • 您好像有错字。 std::declval&lt;T&gt; -> std::declval&lt;C&gt;.
  • @n。 '代词' m。你是完全正确的。我误解了在这种情况下替换是如何工作的,但是当你指出我的错误时,我看到了我的愚蠢。如果这对其他人有帮助,我将尝试编写一个解决方案。否则,如果这更有帮助,我可以删除这个问题。我是新发帖者,因此不胜感激。
  • 自己写一个对他人有帮助的答案比删除 IMO 问题要好得多。
  • 我写了一些东西。任何有关如何改进答案或解决我对此 GCC 行为缺乏理解的反馈表示赞赏。
  • 我会稍等一下,看看其他人是否想提出更完整的解释作为答案。否则,我会接受我自己的。感谢大家的帮助。

标签: c++ templates sfinae


【解决方案1】:

如果其他人遇到类似的错误/误解,我的错误(由 n. 'pronouns' m. 恰当地指出)在 has_serialize::test 中使用了错误的类型。从我可以推断(在我的天真中)是因为

template<typename T>
struct has_serialize
{
    // ...
    template<typename C>
    static constexpr auto test(int)
      -> decltype(std::declval<T>().serialize(std::declval<SerializerBase&>()), std::true_type());
    // ...
};

模板C 没有出现在has_serialize::test 中,因此GCC 不提供替换失败的机会。因此,GCC 会尝试评估std::declval&lt;T&gt;().serialize(...),并且在T = std::vector&lt;double&gt; 时显然会抛出错误。如果在这一行中T 被替换为C,则编译器将其识别为替换失败并确定签名不合适。

此外,正如 Maxim Egorushkin 所指出的,T::serialize 可能会返回重载operator, 的用户定义类型。为了改善(尽管不太可能)潜在错误,代码应该是:

template<typename T>
struct has_serialize
{
    // ...
    template<typename C>
    static constexpr auto test(int)
      -> decltype(static_cast<void>(std::declval<C>().serialize(std::declval<SerializerBase&>())), std::true_type());
    // ...
};

【讨论】:

  • 我不清楚为什么 GCC 会在替换 T 时尝试评估语句,而如果使用 C,它会识别替换失败(特别是因为无论使用 T 还是 C,MSVC 的行为都相同) .
  • serialize 的结果可能是一个重载 operator, 的 UDT,因此它不会返回 std::true_type。为了解决这个问题,您可能希望在应用逗号运算符之前将serialize 的结果转换为void,例如decltype(static_cast&lt;void&gt;(std::declval&lt;T&gt;().serialize(..)), std::true_type()).
  • 您的观点很好,我将编辑答案以包含此信息。我在我的代码中知道返回类型绝不是 UDT,但您建议了一个很好的技巧来避免出现问题(如果可能的话)。
  • MSVC 在处理模板方面相当不标准。直到最近,它甚至还没有两阶段查找。现在不确定。也许这种差异与此有关。无论如何,T 在那里,test 的返回类型中没有替换(因此没有 SFINAE),因为模板参数 of test 没有出现在其中。周围类模板的模板参数无关。
  • @n。 '代词' m。您的解释在进一步思考时非常有意义。我剩下的唯一问题是为什么当使用T 代替C 时MSVC 可以工作。我一般都知道 MSVC 处理模板与 GCC(和其他编译器)完全不同,但出于我自己的好奇心,我希望我知道幕后发生了什么(至少在概念上)。
猜你喜欢
  • 1970-01-01
  • 2020-05-11
  • 1970-01-01
  • 2013-02-20
  • 1970-01-01
  • 1970-01-01
  • 2012-05-29
相关资源
最近更新 更多