模板加约束,其中有迷思,着意分析一下。

template <typename T>
void f(T t) {}

template <typename T>
void f(T t) requires true  {}

f(1); // the second one wins

二比一特殊,决战胜出,似乎全在预料之内。

根据 [temp.over.link]/7

Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types, parameter lists, and trailing requires-clauses (if any) that are equivalent using the rules described above to compare expressions involving template parameters. Two function templates are functionally equivalent if they are declared in the same scope, have the same name, accept and are satisfied by the same set of template argument lists, and have return types and parameter lists that are functionally equivalent using the rules described above to compare expressions involving template parameters. If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.

首先要明白函数模板的语法等价和功能等价。如果函数模板位于相同作用域,名称相同,模板头形式等价,返回类型、参数列表、TRC 中涉及模板参数的表达式也等价,则函数模板语法等价;如果函数模板位于相同作用域,名称相同,接受并满足同样的模板参数列表,返回类型、参数列表中涉及模板参数的表达式功能也等价,那么函数模板功能等价。

比如:

// guaranteed to be the same
template <int I> void f(A<I>, A<I+10>);
template <int I> void f(A<I>, A<I+10>);

// guaranteed to be different
template <int I> void f(A<I>, A<I+10>);
template <int I> void f(A<I>, A<I+11>);

// ill-formed, no diagnostic required
template <int I> void f(A<I>, A<I+10>);
template <int I> void f(A<I>, A<I+1+2+3+4>);

而在开始的例子中,两个函数模板功能等价,语法不等价,存在 IFNDR。

如果皆等价,便会出现重定义错误。例如:

template <typename>
concept bool Fooable = true;

template <typename T>
    requires Fooable<T>
void do_something(T&) {}

template <Fooable T>
void do_something(T&) {}

int main() {
    int i = 0;
    do_something(i); // redefinition
}

再看一个例子:

template <class T>
T&& declval() noexcept requires true;

template <class T>
T declval() noexcept;

这两个函数模板的功能并不等价,因为返回类型中模板参数构成的表达式功能不侔。而重载决议不看返回类型,只看名称和参数,故正常情况下,决议会失败。增加一个 requires true 提升第一个函数模板的特殊性,能消除歧义。

借助这个技巧重写 declval 带来极大的简易性,最早的某个实现是这样的:

template<typename _Tp, typename _Up = _Tp&&> // template 1
_Up __declval(int);

template<typename _Tp> // template 2
_Tp __declval(long);

template<typename _Tp> // template 3
auto declval() noexcept -> decltype(__declval<_Tp>(0));

需要使用额外的辅助函数,在其中添加模板参数,来区分不同情况,而 C++20 的这种办法则优雅且充满技巧性,除了上面据说的技巧,它还利用 SFINAE 来提供次选函数。

再看 [/async/env.hpp] 中的代码:

constexpr inline struct get_env_t {
    template <typename T>
        requires true // more constrained
    constexpr auto operator()(T &&t) const
        noexcept(noexcept(tag_invoke(std::declval<get_env_t>(),
                                     std::forward<T>(t))))
            -> decltype(tag_invoke(*this, std::forward<T>(t))) {
        return tag_invoke(*this, std::forward<T>(t));
    }

    constexpr auto operator()(auto &&) const -> empty_env { return {}; }
} get_env{};

其实同理,也是返回类型中模板参数构成的表达式不同,函数模板功能不等价,再以 requires true 消除歧义。

因此,若是函数模板要重载,参数也相同,只要破坏其功能等价,再以 requires true 消除重载决议的歧义问题即可。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.