【问题标题】:Template Specialization With Callable Argument Not Working As Expected具有可调用参数的模板专业化未按预期工作
【发布时间】:2018-04-13 17:03:47
【问题描述】:

下面的代码在dp1“term 不计算为带 0 个参数的函数”的行上抱怨。我希望 dp1 会匹配 IsInstantiation 的特化 Dispatch,但它会出错。

如何更改此代码以使其按预期工作?我用的是VS2015,不能用std::invokestd::is_invokable

#include <type_traits>

template <typename>
class TemplateA {};

struct Callable
{
    void operator()() {};
};

template <typename F, typename... Args>
constexpr auto Invoke(F &&f, Args &&... args)
{
    return f(std::forward(args)...);
}

template <template<typename...> class TT, typename T>
struct IsInstantiation : std::false_type {};

template <template<typename...> class TT, typename... Ts>
struct IsInstantiation<TT, TT<Ts...>> : std::true_type {};

template <typename, typename = void>
struct HasNoArgs : std::false_type {};

template <typename T>
struct HasNoArgs<T, std::void_t<decltype(Invoke(T{}))>> : std::true_type {};

template <typename T1, typename = void>
class Dispatch;

template <typename T1>
class Dispatch<T1, std::enable_if_t<HasNoArgs<T1>::value>> {};

template <typename T1>
class Dispatch<T1, std::enable_if_t<IsInstantiation<TemplateA, T1>::value>> {};

int main(int argc, char *argv[])
{
    Dispatch<TemplateA<int>> dp1;
    Dispatch<Callable> dp2;
    return 0;
}

【问题讨论】:

  • 相关:在 C++11 中使用 INVOKE:stackoverflow.com/q/32918679/1896169
  • 我将HasNoArgs 重命名为CanInvokeWithNoArgs,因为您要问两个问题,1. 我们可以为类型调用Invoke 吗?和 2. 我们可以不带参数地做到这一点吗?

标签: c++ c++11


【解决方案1】:

您的代码有几个问题。

  1. 这是对forward的不当使用:

    return f(std::forward(args)...);
    

    std::forward 的函数参数是故意的非推导上下文。所以这个调用永远不会成功。你要f(std::forward&lt;Args&gt;(args)...)

  2. Invoke 对 SFINAE 不友好:

    template <typename F, typename... Args>
    constexpr auto Invoke(F &&f, Args &&... args)
    { ... }
    

    如果F 确实可以用Args... 调用,则此函数将正常工作。如果不是,那么这是一个硬编译错误,因为问题只出现在 body 中,它在替换的“直接上下文”之外。因此,这不是“替换失败”(SFINAE 中的 SF),而是实例化失败。在HasNoArgs 中,您正在尝试查看是否可以调用它——这意味着您需要替换失败。为此,您需要将表达式提升到直接上下文中:

    template <typename F, typename... Args>
    constexpr auto Invoke(F &&f, Args &&... args)
        -> decltype(f(std::forward<Args>(args)...))
    { return f(std::forward<Args>(args)...); }
    

    现在,它对 SFINAE 友好(此外,还可以返回引用)。

  3. 您的HasNoArgs 检查的次数比您想象的要多:

    template <typename T>
    struct HasNoArgs<T, std::void_t<decltype(Invoke(T{}))>> : std::true_type {};
    

    这要求T 可以在没有参数的情况下被调用,在修复了上面的#1 和#2 之后,现在可以实现。但它要求T{} 是一个有效的表达式。不可默认构造的可调用对象将无法通过此测试,但不是因为测试声称它正在寻找的原因。要解决这个问题,请使用std::declval:

    template <typename T>
    struct HasNoArgs<T, std::void_t<decltype(Invoke(std::declval<T>()))>> : std::true_type {};
    

【讨论】:

  • 我可能会写 std::forward&lt;F&gt;(f)(std::forward&lt;Args&gt;(args)...) 以便它调用适当的 ref-qualified operator() 重载。
  • 我通常在std::forward 上有模板参数,我只是在这里忘记了它,编译器没有捕捉到它。我在早期版本中正是出于这个原因使用了std::declval,但是当我写这个简单的例子时忘记了为什么。
猜你喜欢
  • 2011-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-06
  • 2020-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多