根据特征隐藏 class 个模板实例

Hide class template instance based on traits

我有一个特征 class 如下反映了两种类型之间的兼容性:

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

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

支持此功能的简单 class 如下:

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;
};

我想编写一个使用此特征 class 的工人 class 并在可用时调用特殊方法。基本上类似于以下内容:

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 会导致编译错误:

In static member function ‘static void Worker::SpecialMethodAvailable>::type>::DoSomething(T, GlobalDataType&)’: source.cpp:38:15: error: request for member ‘SpecialMethod’ in ‘globalData’, which is of non-class type GlobalDataType {aka int}’

有人可以解释为什么这在 GCC 下无法正常工作吗?有什么替代方案?

Link to online compiler

Mvsc 14 不执行模板所需的 2 个阶段 look-up。

gcc 可以(而且是正确的)。

globalData.SpecialMethod(t); 是不正确的任何 t 立即所以错误。 (globalData.SpecialMethod 不正确,不依赖于模板参数)。

通过post-pone的评价您可能得到您想要的:

template <typename T>
struct Worker<T, std::enable_if_t<Traits<GlobalDataType, T>::SpecialMethodAvailable>>
{
    template <typename G, typename U>
    static void f(G& g, U& u)
    {
        g.SpecialMethod(u);
    }

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

Demo

正如 Jarod42 所解释的那样,此方法

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

with GlobalDataType fixed as 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); }

What would be alternatives?

我向您推荐一种完全不同的方法,基于(遵循 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);

现在你可以写一个模板函数的声明 return 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 class 和专业变成

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); }
 };