Suppose you have a family of type-unrelated classes implementing a common concept by means of a given method returning a value:
class A { public: int val() const { ... } };
class B { public: int val() const { ... } };
suppose you need a generic free function taking a T
returning a conventional value for whatever type NOT implementing the val
method or calling the val
method for ALL the types that has one:
template<class T> int val_of(const T& t) { return 0; }
template<class T> int val_of(const T& t) { return t.val(); }
Consider that A and B are just samples: you don't know how many types will ever exist implementing val
, and how many types will exist not implementing it (hence explicit specialization won't scale).
Is there a simple way, based on the C++ standards, to come to a way to statically select the val_of
version?
I was thinking to a std::conditional
or std::enable_if
, but I didn't find a simple way to express the condition.
Simple answer: you cannot.
Use Static Member Functions Static member functions are the functions of a class that do not need an object to call them. They can be called directly with the class name using the scope resolution operator :: .
No, it's not necessary.
Classes and their member functions (or methods) are integral features of the object-oriented C++ programming language. By tying these functions to an object's namespace, class methods make your C++ code modular and reusable.
You can use return-type SFINAE:
template<typename T>
auto val_of(const T& t) -> decltype(std::declval<T>().val())
{
return t.val();
}
int val_of(...)
{
return 0;
}
Just a somewhat longer comment... Your question has been answered. But I recently had a similar problem. Say you want to write a method to print strings to cout
: Use member function write(std::cout)
, if not available use free function to_string()
, if not available fallback to operator<<
. You can use expression SFINAE as in the answer and a little class hierarchy to disambiguate the overloads:
struct S3 {};
struct S2 : S3 {};
struct S1 : S2 {};
template <class T>
auto print(S1, T const& t) -> decltype(t.write(std::cout)) {
t.write(std::cout);
}
template <class T>
auto print(S2, T const& t) -> decltype(std::cout << to_string(t)) {
std::cout << to_string(t);
}
template <class T>
void print(S3, T const& t) {
std::cout << t;
}
template <class T>
void print(T const& t) {
print(S1(), t);
}
Use this type trait class to determine whether a type has got val
member function.
template<typename U>
struct has_val {
typedef char yes;
typedef struct { char c[2]; } no;
template<typename X>
static constexpr auto test(int) -> decltype( std::declval<X>().val(), yes() );
template<typename X>
static constexpr no test(...);
static constexpr bool value = sizeof(test<U>(0)) == sizeof(yes);
};
You can use it as a condition to enable_if
.
The currently highest voted answer invokes undefined behavior in some cases, so I will give an alternative answer.
We start with some boilerplate machinery:
template<typename T> struct type_sink { typedef void type; }
template<typename T> using TypeSink = typename type_sink<T>::type;
Then a has_val
traits class:
template<typename T, typename=void>
struct has_val : std::false_type;
template<typename T>
struct has_val<T, TypeSink< decltype(std::declval<T>().val()) > > : std::true_type;
We can then use tag dispatching to solve your problem:
template<typename T>
int val_of_internal( T const& t, std::true_type /* has_val */ ) {
return t.val();
}
template<typename T>
int val_of_internal( T const& t, std::false_type /* has_val */ ) {
return 0;
}
template<typename T>
int val_of( T const& t ) {
return val_of_internal( t, has_val<T const>() );
}
If you find writing has_val
tiresome and prefer macros, here is a set of macros that write the has_val
for you:
#define EXPRESSION_IS_VALID_FOR_TYPE_T_TRAIT( TRAIT_NAME, ... ) \
template<typename T, typename=void> \
struct TRAIT_NAME : std::false_type {}; \
template<typename T> \
struct TRAIT_NAME< T, TypeSink< decltype( __VA_ARGS__ ) > >: std::true_type {}
#define HAS_NULLARY_METHOD_TRAIT(TRAIT_NAME, METHOD_NAME) \
EXPRESSION_IS_VALID_FOR_TYPE_T_TRAIT( TRAIT_NAME, std::declval<T>().METHOD_NAME() )
Now we can write this:
HAS_NULLARY_METHOD_TRAIT( has_val, val );
but I do not know if it is worth it.
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