【问题标题】:Using alias templates for sfinae: does the language allow it?为 sfinae 使用别名模板:语言允许吗?
【发布时间】:2018-08-28 01:37:47
【问题描述】:

我刚刚发现了以下技术。它看起来非常接近提议的概念语法之一,在 Clang、GCC 和 MSVC 上完美运行。

template <typename T, typename = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type>
using require_rvalue = T&&;

template <typename T>
void foo(require_rvalue<T> val);

我试图用“sfinae in type alias”之类的搜索请求找到它,但什么也没得到。这种技术有名字吗?语言真的允许吗?


完整示例:

#include <type_traits>

template <typename T, typename = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type>
using require_rvalue = T&&;

template <typename T>
void foo(require_rvalue<T>)
{
}

int main()
{
    int i = 0;
    const int ic = 0;
    foo(i);            // fail to compile, as desired
    foo(ic);           // fail to compile, as desired
    foo(std::move(i)); // ok
    foo(123);          // ok
}

【问题讨论】:

标签: c++ c++11 language-lawyer sfinae


【解决方案1】:

[...] 语言真的允许吗?

不能说名字,但在我看来这是肯定的。

相关的写法是[temp.alias]/2:

当一个template-id指代一个别名模板的特化时,它等价于用它的template-arguments代替得到的关联类型>template-parameters 在别名模板的 type-id 中。

还有 sfinae 规则,[temp.deduct]/8:

只有在函数类型、其模板参数类型及其显式说明符的直接上下文中的无效类型和表达式会导致推导失败。

采用require_rvalue&lt;T&gt; 类型的参数确实表现得好像我们替换了该别名,这给我们一个T&amp;&amp; 或替换失败 - 并且替换失败可以说是在直接上下文中 替换,因此是“对 sfinae 友好”,而不是硬错误。请注意,即使未使用默认类型参数,由于CWG 1558void_t 规则),我们添加了[temp.alias]/3

但是,如果 template-id 是依赖的,则后续模板参数替换仍然适用于 template-id

这确保我们仍然替换到默认类型参数以触发所需的替换失败。

问题的第二个未说明部分是这是否真的可以充当转发引用。规则在[temp.deduct.call]/3:

转发引用是对不代表类模板的模板参数的 cv 非限定模板参数的右值引用(在类模板参数推导期间([over.match.class.deduct ]))。如果 P 是转发引用且参数是左值,则使用类型“对 A 的左值引用”代替 A 进行类型推导。

具有一个模板参数的别名模板,其关联类型是对其 cv 非限定模板参数的右值引用,是否被视为转发引用?好吧,[temp.alias]/2 说require_rvalue&lt;T&gt; 等价于T&amp;&amp;,而T&amp;&amp; 是正确的。所以可以说......是的。

所有的编译器都这样对待它,这当然是一个很好的验证。


虽然,请注意CWG 1844 的存在和缺乏直接上下文的实际定义,以及那里的示例也依赖于默认参数的替换失败 - 问题指出存在实现分歧.

【讨论】:

  • 有趣的是,别名替换必须发生在类型推导之前,以便实现参数是转发引用并可以推导T。并且别名替换必须在类型推导之后发生,以确定默认模板参数是否有效。但是,我很确定,这正是 [temp.alias]/4 的含义“但是,如果模板 ID 是依赖的,则后续模板参数替换仍适用于模板 ID。”
  • 是的,我注意到了我的评论,而您的编辑只隔了几秒钟。
  • 是的,这是通过大力挥手指定的标准的一部分。
【解决方案2】:

它可以工作并且被允许,因为它依赖于标准允许的广泛使用的 C++ 功能:

  1. 函数参数中的SFINAE([temp.over]/1[temp.deduct]/6[temp.deduct]/8):

    template <typename T>
    void foo(T&& v, typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = nullptr)
    { /* ... */ }
    

    我们无法推断出像 void foo(typename std::enable_if&lt;std::is_rvalue_reference&lt;T&amp;&amp;&gt;::value, T&gt;::type&amp;&amp;) (CWG#549) 这样的实际参数,但可以使用模板别名解决此限制(这是我在问题中提出的技巧)

  2. 模板参数声明中的 SFINAE ([temp.deduct]/7):

    template <typename T, typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type* = nullptr>
    void foo(T&& v)
    { /* ... */ }
    
  3. 函数参数中的别名模板 ([temp.alias]/2):

    template<class T> struct Alloc { /* ... */ };
    template<class T> using Vec = vector<T, Alloc<T>>;
    
    template<class T>
      void process(Vec<T>& v)
      { /* ... */ }
    
  4. 别名模板可以有默认参数([temp.param]/12[temp.param]/15[temp.param]/18

  5. 用可推导类型参数化的别名模板的模板参数仍然可以推导([temp.deduct.type]/17):

我已经接受了@Barry 的回答并提出了这个(包含集中的信息和技巧使用的各个方面),因为很多人(包括我)都害怕 C++ 标准巫毒语言关于模板推导的东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多