成员函数中的 decltype(auto) 忽略无效主体,decltype(expr) 失败
decltype(auto) in member function ignores invalid body, decltype(expr) fails
我有一个简单的模板化包装器结构,其成员函数在其模板类型的对象上调用 .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
如果我使用我认为是尾随 return 类型的语义等价物,它会在变量声明中出错
template <typename T>
struct Wrapper {
T t;
auto f() -> decltype(t.error()) {
return t.error();
}
};
Wrapper<int> w; // error here
我承认这两者在语义上并不等价,但是无论如何可以使用尾随 return 类型(仅限 C++11)来获得前者的行为,而无需专门化整个 class 有某种 HasError
tmp 诡计?
版本差异
decltype(auto) f();
auto f() -> decltype(t.error());
是第二个的函数声明可以无效。 Return 函数模板的类型推导发生在实例化 定义 时 [dcl.spec.auto]/12。尽管我找不到任何有关 class 模板成员函数的 return 类型推导的信息,但我认为它们的行为相似。
隐式实例化 class 模板 Wrapper
会导致实例化 声明 ,但不会实例化 定义 所有(非虚拟)成员函数 [temp.inst]/1。声明 decltype(auto) f();
有一个非推导的占位符但有效。另一方面,auto f() -> decltype(t.error());
有一些实例化的无效 return 类型。
C++11 中的一个简单解决方案是推迟 return 类型的确定,例如通过将 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.
但是,我怀疑该规则已被引入 allow,但不要求实现检查非实例化模板中的错误。在这种情况下,源代码没有编程错误;该错误将发生在源代码描述的 class 模板的实例化中。所以,我觉得应该没问题。
另一种解决方案是使用后备 return 类型使函数 声明 格式正确,即使 定义 不是(对于所有实例化):
#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();
}
};
一种方法是标记的 crtp。
// todo:
template<class T>
struct has_error; // true_type if T.error() is valid
template<class D,class T,bool Test=has_error<T>{}>
struct do_whatever {
D* self(){return static_cast<D*>(this);}
D const* self()const{return static_cast<D const*>(this);}
auto f()->decltype(self()->t.error()) {
return self()->t.error();
}
};
template<class D,class T>
struct do_whatever<D,T,false>{};
如果 T
没有 error()
,现在 f()
就不存在了。
我有一个简单的模板化包装器结构,其成员函数在其模板类型的对象上调用 .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
如果我使用我认为是尾随 return 类型的语义等价物,它会在变量声明中出错
template <typename T>
struct Wrapper {
T t;
auto f() -> decltype(t.error()) {
return t.error();
}
};
Wrapper<int> w; // error here
我承认这两者在语义上并不等价,但是无论如何可以使用尾随 return 类型(仅限 C++11)来获得前者的行为,而无需专门化整个 class 有某种 HasError
tmp 诡计?
版本差异
decltype(auto) f();
auto f() -> decltype(t.error());
是第二个的函数声明可以无效。 Return 函数模板的类型推导发生在实例化 定义 时 [dcl.spec.auto]/12。尽管我找不到任何有关 class 模板成员函数的 return 类型推导的信息,但我认为它们的行为相似。
隐式实例化 class 模板 Wrapper
会导致实例化 声明 ,但不会实例化 定义 所有(非虚拟)成员函数 [temp.inst]/1。声明 decltype(auto) f();
有一个非推导的占位符但有效。另一方面,auto f() -> decltype(t.error());
有一些实例化的无效 return 类型。
C++11 中的一个简单解决方案是推迟 return 类型的确定,例如通过将 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.
但是,我怀疑该规则已被引入 allow,但不要求实现检查非实例化模板中的错误。在这种情况下,源代码没有编程错误;该错误将发生在源代码描述的 class 模板的实例化中。所以,我觉得应该没问题。
另一种解决方案是使用后备 return 类型使函数 声明 格式正确,即使 定义 不是(对于所有实例化):
#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();
}
};
一种方法是标记的 crtp。
// todo:
template<class T>
struct has_error; // true_type if T.error() is valid
template<class D,class T,bool Test=has_error<T>{}>
struct do_whatever {
D* self(){return static_cast<D*>(this);}
D const* self()const{return static_cast<D const*>(this);}
auto f()->decltype(self()->t.error()) {
return self()->t.error();
}
};
template<class D,class T>
struct do_whatever<D,T,false>{};
如果 T
没有 error()
,现在 f()
就不存在了。