Reputation: 2854
I'm currently writing a class that has some fancy templates to define some of its members, but some of those templates are exactly the same. Consider the following code (these are all class members):
template <typename T, typename = enable_if_t<is_convertible<int, T>::value>>
T get() const { return convert_self_to_int(); }
template <typename T, typename = enable_if_t<is_constructible<T, string>::value>, typename = void>
T get() const { return convert_self_to_string(); }
template <typename T, typename = enable_if_t<is_convertible<int, T>::value>>
operator T() const { return get<T>(); }
template <typename T, typename = enable_if_t<is_constructible<T, string>::value>, typename = void>
operator T() const { return get<T>(); }
As you can see, I have a templated member function called get
, which uses long and somewhat hard to read template code.
This part is not essential for the question, but here's a brief explanation of all those fancy templates:
get
is a function which can return data in one of two formats: if the template argumentT
is a type into which anint
can be converted, then the integral representation of the data is returned (thus triggering the conversion to the requested type, which we know is possible). IfT
is something that can be constructed from astring
, then thestring
representation of the data is returned (again, triggering the construction ofT
from astring
). Any other type that does not fall into these categories will simply cause a compile-time error, which is exactly what this code is intended to do.
This class also has simple conversion operators defined, which are written in terms of get
.
Now since these operators use the exact same templates as the corresponding definitions of get
, can I somehow avoid duplicating all that nasty template code? Can I reuse a line of template code to define multiple things, making the code more readable?
Upvotes: 3
Views: 896
Reputation: 302817
You can just have operator T()
forward to get<T>
using SFINAE. That way, you only need one operator T()
:
template <class T, class = decltype(std::declval<ClassName>().get<T>())>
operator T() const {
return get<T>();
}
Also, for the multiple SFINAEs, instead of continually adding additional typename=void
s you can change your enable_if_t
to give you a defaulted int
:
template <class T, std::enable_if_t<std::is_convertible<int, T>::value, int> = 0>
T get() const { return convert_self_to_int(); }
template <class T, std::enable_if_t<std::is_convertible<std::string, T>::value, int> = 0>
T get() const { return convert_self_to_string(); }
Now, that won't work in clang unfortunately so I'd simply suggest flipping the ordering. Have the operator T()
be SFINAE-d:
template <class T, std::enable_if_t<std::is_convertible<int, T>::value, int> = 0>
operator T() const { return convert_self_to_int(); }
template <class T, std::enable_if_t<std::is_convertible<std::string, T>::value, int> = 0>
operator T() const { return convert_self_to_string(); }
And just have get
forward along:
template <class T> T get() const { return operator T(); }
The advantage here is that we're not duplicating anything and the std::is_convertible<>
type trait will work correctly - since operator T()
is SFINAE-d. A test on get<T>()
will fail, but that doesn't seem like something that is commonly testable.
Upvotes: 2