使用sfinae的别名模板:语言允许吗?

Nik*_*zev 26 c++ sfinae language-lawyer c++11

我刚刚发现了以下技术.它看起来非常接近提议的概念语法之一,完全适用于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);
Run Code Online (Sandbox Code Playgroud)

我试图通过搜索请求找到它,比如"类型别名中的sfinae"并没有得到任何结果.这种技术有没有名称,语言实际上允许它吗?


完整的例子:

#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
}
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 14

[...]语言实际上允许它吗?

不能说出这个名字,但这似乎对我来说是肯定的.

相关措辞是[temp.alias]/2:

模板id是指一个别名模板的专业化,它相当于通过其置换得到的相关联的类型的模板的参数模板参数类型ID别名模板.

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

只有函数类型的直接上下文中的无效类型和表达式,其模板参数类型及其显式说明符才会导致演绎失败.

以类型的参数require_rvalue<T>确实表现得好像我们替代品的别名,这无论是给了我们一个T&&或替换故障-和替代失败可以说是直接背景替代的,因此是"SFINAE友好"而不是作为一个很难的错误.请注意,即使默认类型参数未使用,作为CWG 1558(void_t规则)的结果,我们添加了[temp.alias]/3:

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

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

问题的第二个未说明的部分是这实际上是否可以作为转发参考.那里的规则在[temp.deduct.call]/3中:

转发参考是一个rvalue参考,并不代表一个类模板的模板参数的cv-不合格模板参数(类模板参数推导过程中([over.match.class.deduct])).如果P是转发引用且参数是左值,则使用类型"对A的左值引用"代替A来进行类型推导.

是一个带有一个模板参数的别名模板,其关联类型是对其cv-nonqualified模板参数的rvalue引用,该参数被视为转发引用?好吧,[temp.alias]/2表示require_rvalue<T>相当于T&&,并且T&&是正确的.所以可以说......是的.

并且所有编译器都将其视为这样,这当然是一个很好的验证.


尽管如此,请注意CWG 1844的存在以及缺乏对直接上下文的实际定义,并且那里的例子也依赖于默认参数的替换失败 - 该问题表明实现差异.

  • 是的,这是有力的handwaving指定的标准的一部分. (2认同)

Nik*_*zev 5

它之所以有效并被允许,是因为它依赖于标准允许的广泛使用的 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)
    { /* ... */ }
    
    Run Code Online (Sandbox Code Playgroud)

    我们无法推断出像void foo(typename std::enable_if<std::is_rvalue_reference<T&&>::value, T>::type&&)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)
    { /* ... */ }
    
    Run Code Online (Sandbox Code Playgroud)
  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)
      { /* ... */ }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 别名模板可以具有默认参数([temp.param]/12[temp.param]/15[temp.param]/18

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

我已经接受了 @Barry 的答案并放置了这个(包含集中信息以及关于该技巧使用的各个方面),因为很多人(包括我)都害怕 C++ 标准巫术语言中关于模板推导的内容。