在定义重载的C++函数模板的原型时,使用其名称来引用先前的定义是否合法?

Гри*_*ков 5 c++ language-lawyer c++11

假设我们想要重载函数模板f,但仅在尚未声明类似的重载时:

template<typename T>
void f(T); // main prototype

struct A {};
struct B {}; 

//we want to declare B f(A), but only if something like A f(A) hasn't been declared
//we can try to check the type of expression f(A) before defining it
//and disable overload via enable_if
template<typename T = void>   //it has to be a template to use enable_if
std::enable_if_t<std::is_same_v<void, decltype(T(), (f)(A{}))>, B> f(A);
// decltype expression depends on T, but always evaluates to the type of (f)(A{})
// parenthesis are used to disable ADL, so only preceding definitions are visible
Run Code Online (Sandbox Code Playgroud)

代码被Clang接受,在GCC上有用(参见 https://godbolt.org/g/ZGfbDW),并且在Visual C++ 15.5上导致编译器错误"递归类型或函数依赖性上下文过于复杂".

我的问题是:这是根据C++标准的法律声明还是涉及未定义的行为?

Bar*_*rry 2

我相信这是一个法律声明,只是因为没有任何理由不这样做。

有几件事值得指出。首先,我们f这里有两种用途,它们是不同的:

template<typename T = void>
std::enable_if_t<std::is_same_v<void, decltype(T(), (f)(A{}))>, B> f(A);
//                                                  ^^^           ^^^
//                                                   #1            #2
Run Code Online (Sandbox Code Playgroud)

f我们在 中声明该名称#2,但它不在#1中的使用范围内- 它的声明点位于完整声明符之后,其中包括enable_if_t块。所以这里没有递归。如果 VS 有问题,我怀疑这可能与模板中名称查找的一般问题有关。

其次,这些模板在功能上并不等效 - 主原型采用一个类型为模板参数的参数,而这个采用A. 功能等效性的要点是以不需要逐个标记复制的方式将声明与定义匹配 - 但f本示例中的两个函数模板完全不同。

我不认为这是格式错误的原因。令人皱眉,是的。格式不正确,不。


当尝试声明返回类型取决于其自身的递归函数模板时,这会导致相当常见的错误。例如:

template <size_t V>
using size_ = std::integral_constant<size_t, V>; 

constexpr size_<0> count() { return {}; }

template <typename T, typename... Ts> 
constexpr auto count(T, Ts... ts)
    -> size_<decltype(count(ts...))::value + 1> { return {}; }

int main() {
    static_assert(count() == 0);      // ok
    static_assert(count(1) == 1);     // ok
    static_assert(count(1, 2) == 2);  // error: no matching function to call
}
Run Code Online (Sandbox Code Playgroud)