我有一个如下的traits类,它反射(reflect)了两种类型之间的兼容性:

template <typename ObjectType, typename ArgumentType>
struct Traits
{
    static const bool SpecialMethodAvailable = false;
};

单个成员确定是否可以在参数为SpecialMethod()ObjectType类型的对象上调用ArgumentType

支持此的简单类如下:
class ClassWithSpecialMethod
{
public:
    template <typename T>
    void SpecialMethod(T param) { std::cout << "Special Method called with " << param << std::endl; }
};

template <typename ArgumentType>
struct Traits<ClassWithSpecialMethod, ArgumentType>
{
    static const bool SpecialMethodAvailable = true;
};

我想编写一个使用此traits类的worker类,并在可用时调用特殊方法。基本上如下所示:
template <typename T>
struct Worker
{
    static void DoSomething(T t, GlobalDataType& globalData)
    {
        //if Traits<GlobalDataType, T>::SpecialMethodAvailable
        //    call the method
        //else
        //    do something different
    }
};

我试图使用std::enable_if实现这一点。我的解决方案适用于Visual C 14.1编译器,但不适用于GCC。这是我尝试过的:
template <typename T, typename Enable = void>
struct Worker
{
    static void DoSomething(T t, GlobalDataType& globalData)
    {
        std::cout << "There is no special method (called with " << t << ")" << std::endl;
    }
};

template <typename T>
struct Worker<T, typename std::enable_if<Traits<GlobalDataType, T>::SpecialMethodAvailable>::type>
{
    static void DoSomething(T t, GlobalDataType& globalData)
    {
        globalData.SpecialMethod(t);
    }
};

我使用它如下:
typedef ... GlobalDataType; //before the template declarations

int main()
{
    GlobalDataType td;

    int integer = 0;
    Worker<int>::DoSomething(integer, td);
}

如果将GlobalDataType类型定义为ClassWithSpecialMethod,则VS和GCC都可以正常编译并正确输出:
Special Method called with 0

但是,如果将GlobalDataType类型化为不允许使用特殊方法的内容(例如int),则VS仍会生成正确的输出,而GCC会导致编译错误:



有人可以解释为什么这在GCC中无法按预期进行吗?什么是替代品?

Link to online compiler

最佳答案

如Jarod42所述,此方法

static void DoSomething(T t, GlobalDataType& globalData)
{
    globalData.SpecialMethod(t);
}

GlobalDataType固定为int,这是永远错误的(对于T类型),因为可以确定int没有SpecialMethod()

要用最少的代码更改来解决此问题,可以对第二个参数进行模板化
template <typename U>
 static void DoSomething(T t, U & globalData)
  { globalData.SpecialMethod(t); }

如果您希望DoSomething()仅接收GlobalDataType(作为第二个参数),则可以将其强制设置为使用SFINAE启用DoSomething,只有UGlobalDataType。像
template <typename U>
 static typename std::enable_if<std::is_same<U, GlobalDataType>{}>
   DoSomething(T t, U & globalData)
 { globalData.SpecialMethod(t); }



我向您提出了一种完全不同的方法,基于(在std::declval()示例之后)函数声明之上。

首先,几个模板助手功能
template <typename ObjectType, typename ... Args>
constexpr auto withSpecialMethodHelper (int)
   -> decltype(std::declval<ObjectType>.SpecialMethod(std::declval<Args>...),
               std::true_type{} );

template <typename ... Args>
constexpr std::false_type withSpecialMethodHelper (long);

现在,您可以编写模板函数的声明,如果std::true_type具有可以用各种类型的ObjectType参数的可变列表调用的SpecialMethod(),则返回Args...
template <typename ObjectType, typename ... Args>
constexpr auto withSpecialMethod ()
   -> decltype( withSpecialMethodHelper<ObjectType, Args...>(0) );

也许更好,正如Jarod42所建议的那样,通过using
template <typename ObjectType, typename ... Args>
using withSpecialMethod
   = decltype( withSpecialMethodHelper<ObjectType, Args...>(0) );

如果可以使用C++ 14,则还可以定义withSpecialMethod_v模板constexpr变量
template <typename ObjectType, typename ... Args>
constexpr bool withSpecialMethod_v
  = decltype(withSpecialMethod<ObjectType, Args...>())::value;

如果声明了函数或
template <typename ObjectType, typename ... Args>
constexpr bool withSpecialMethod_v
   = withSpecialMethod<ObjectType, Args...>::value;

如果是using,则可以简化使用。

现在Worker类和特化成为
template <typename T, bool = withSpecialMethod_v<GlobalDataType, T>>
struct Worker
 {
    static void DoSomething (T t, GlobalDataType & globalData)
    {
        std::cout << "There is no special method (called with " << t << ")"
         << std::endl;
    }
 };

template <typename T>
struct Worker<T, true>
 {
   template <typename U>
    static void DoSomething(T t, U & globalData)
    { globalData.SpecialMethod(t); }
 };

07-25 21:29