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

Using alias templates for sfinae: does the language allow it?

我刚刚发现了以下技巧。它看起来非常接近提议的概念语法之一,在 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
}

[...] does the language actually allows it?

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

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

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.

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

Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.

采用 require_rvalue<T> 类型的参数确实表现得好像我们替换了那个别名,这给了我们 T&& 或替换失败 - 并且替换失败可以说是在直接上下文中 替换,"sfinae-friendly" 与硬错误相反。请注意,即使未使用默认类型参数,由于 CWG 1558 (the void_t rule), we got the addition of [temp.alias]/3:

However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id.

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

问题的第二个未说部分是这是否真的可以作为转发参考。 [temp.deduct.call]/3:

中的规则

A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

具有一个模板参数的别名模板是否被视为转发引用?嗯,[temp.alias]/2 说 require_rvalue<T> 等价于 T&&T&& 是对的。所以可以说......是的。

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


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

它有效并被允许,因为它依赖标准允许的广泛使用的 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<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)
    { /* ... */ }
    
  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++ 标准巫术语言扣东西