I have a simple templated wrapper struct with a member function calling .error()
on an object of its template type.
template <typename T>
struct Wrapper {
T t;
decltype(auto) f() {
return t.error(); // calls .error()
}
};
If I instantiate this with a type that doesn't have an error()
member function, it's fine as long as I don't call it. This is the behavior I want.
Wrapper<int> w; // no problem here
// w.error(); // uncommented causes compilation failure
If I use what I thought was the semantic equivalent with a trailing return type, it errors on the variable declaration
template <typename T>
struct Wrapper {
T t;
auto f() -> decltype(t.error()) {
return t.error();
}
};
Wrapper<int> w; // error here
I'll accept that the two are not semantically equivalent, but is there anyway to get the behavior of the former using a trailing return type (C++11 only) without specializing the whole class with some kind of HasError
tmp trickery?
The difference between the versions
decltype(auto) f();
auto f() -> decltype(t.error());
is that the function declaration of the second can be invalid. Return type deduction for function templates happens when the definition is instantiated [dcl.spec.auto]/12. Although I could not find anything about return type deduction for member functions of class templates, I think they behave similarly.
Implicitly instantiating the class template Wrapper
leads to the instantiation of the declarations, but not of the definitions of all (non-virtual) member functions [temp.inst]/1. The declaration decltype(auto) f();
has a non-deduced placeholder but is valid. On the other hand, auto f() -> decltype(t.error());
has an invalid return type for some instantiations.
A simple solution in C++11 is to postpone the determination of the return type, e.g. by turning f
into a function template:
template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() );
The definition of that function worries me a bit, though:
template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() )
{
return t.error();
}
For the specializations of Wrapper
where t.error()
is not valid, the above f
is a function template that cannot produce valid specializations. This could fall under [temp.res]/8, which says that such templates are ill-formed, No Diagnostic Required:
If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.
However, I suspect that rule has been introduced to allow, but not require, implementations to check for errors in non-instantiated templates. In this case, there is no programming error in the source code; the error would occur in instantiations of the class template described by the source code. Therefore, I think it should be fine.
An alternative solution is to use a fall-back return type to make the function declaration well-formed even if the definition is not (for all instantiations):
#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();
}
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With