【问题标题】:Hide class template instance based on traits基于特征隐藏类模板实例
【发布时间】:2018-09-08 15:05:33
【问题描述】:

我有一个类似下面的特征类,它反映了两种类型之间的兼容性:

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

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

一个支持这个的简单类如下:

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

我想编写一个使用这个特征类的工作类,并在它可用时调用特殊方法。基本上是这样的:

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

在静态成员函数‘static void Worker::SpecialMethodAvailable>::type>::DoSomething(T, GlobalDataType&)’中: source.cpp:38:15: 错误:请求“globalData”中的成员“SpecialMethod”,这是非类类型 GlobalDataType {aka int}'

有人可以解释为什么这在 GCC 下不能按预期工作吗?有什么替代方案?

Link to online compiler

【问题讨论】:

  • 请注意,std::experimental::is_detected 用专业化替换你的特征:template &lt;typename T, typename Arg&gt; using SpecialMethod_t = decltype(std::declval&lt;T&gt;().SpecialMethod(std::declval&lt;Arg&gt;())); 然后template &lt;typename T, typename Arg&gt; using Traits = std::experimental::is_detected&lt;SpecialMethod_t, T, Arg&gt;;
  • @Raxvan:即使您的解释错误/不准确,您的替代实现仍然有效。
  • @Raxvan - 我同意 Jarod42:我觉得你的解决方案很有趣。

标签: c++ c++11 templates gcc sfinae


【解决方案1】:

正如 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);

现在你可以编写一个模板函数的声明,如果ObjectType 有一个SpecialMethod() 可以用Args... 类型的可变参数列表调用,则返回std::true_type

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

【讨论】:

  • @Jarod42 - 今天我特别笨;你说得对。已更正,谢谢。
  • @Jarod42 - 我不明白如何使用is_detected 是这个上下文;关于最终特征,您是指避免直接使用decltype 并提供方便的constexpr bool value 的类型特征吗?
  • 关于is_detected,我在问题评论中提供。最终特征为template &lt;typename T, typename Arg&gt; using Traits = decltype(withSpecialMethodHelper&lt;ObjectType, Args...&gt;(0));_v 版本为constexpr bool 作为奖励。
【解决方案2】:

Mvsc 14 不执行模板所需的 2 阶段查找。

gcc 确实(并且是正确的)。

globalData.SpecialMethod(t); 对于任何t 都是不正确的,因此错误。 (globalData.SpecialMethod 不正确,不依赖于模板参数)。

通过推迟评估,您可能会得到您想要的:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 2021-10-23
    • 1970-01-01
    • 1970-01-01
    • 2016-04-17
    • 1970-01-01
    相关资源
    最近更新 更多