我有一个简单的模板化包装器结构,其成员函数在其模板类型的对象上调用 .error()
。
template <typename T>
struct Wrapper {
T t;
decltype(auto) f() {
return t.error(); // calls .error()
}
};
如果我用一个没有 error()
成员函数的类型实例化它,只要我不调用它就没问题。这就是我想要的行为。
Wrapper<int> w; // no problem here
// w.error(); // uncommented causes compilation failure
如果我使用我认为是具有尾随返回类型的语义等价物,它会在变量声明上出错
template <typename T>
struct Wrapper {
T t;
auto f() -> decltype(t.error()) {
return t.error();
}
};
Wrapper<int> w; // error here
我承认这两者在语义上并不等价,但是无论如何可以使用尾随返回类型(仅限 C++11)来获得前者的行为,而不需要使用某种 来专门化整个类HasError
tmp 欺骗?
最佳答案
版本之间的区别
decltype(auto) f();
auto f() -> decltype(t.error());
是第二个函数声明可以无效。函数模板的返回类型推导发生在定义被实例化[dcl.spec.auto]/12时。虽然我找不到任何有关类模板成员函数的返回类型推导的信息,但我认为它们的行为相似。
隐式实例化类模板 Wrapper
会导致实例化声明,但不会实例化所有(非虚拟)的定义成员函数[temp.inst]/1。声明 decltype(auto) f();
有一个非推导的占位符但有效。另一方面,auto f() -> decltype(t.error());
对于某些实例化有无效的返回类型。
C++11 中的一个简单解决方案是推迟确定返回类型,例如通过将 f
转换为函数模板:
template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() );
不过,该函数的定义让我有点担心:
template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() )
{
return t.error();
}
对于 Wrapper
的特殊化,其中 t.error()
无效,上面的 f
是一个函数模板,不能产生有效的专长。这可能属于 [temp.res]/8,它表示此类模板格式错误,无需诊断:
If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.
但是,我怀疑该规则已引入允许,但不要求实现检查非实例化模板中的错误。在这种情况下,源代码中没有编程错误;该错误将发生在源代码描述的类模板的实例化中。所以,我觉得应该没问题。
另一种解决方案是使用后备返回类型使函数声明格式正确,即使定义不是(对于所有实例化):
#include <type_traits>
template<typename T> struct type_is { using type = T; };
template <typename T>
struct Wrapper {
T t;
template<typename U=T, typename=void>
struct error_return_type_or_void : type_is<void> {};
template<typename U>
struct error_return_type_or_void
<U, decltype(std::declval<U&>().error(), void())>
: type_is<decltype(std::declval<U&>().error())> {};
auto f() -> typename error_return_type_or_void<>::type {
return t.error();
}
};
关于c++ - 成员函数中的 decltype(auto) 忽略无效主体,decltype(expr) 失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29463711/