At the moment, I have the below working code where using class X, I provide a generic interface for multiple classes - I only expect a static f function to be present, but neither do I fix the return type nor the parameters:
#include <iostream>
#include <type_traits>
class A {
public:
static void f()
{
std::cout << "A::f()" << std::endl;
}
};
class B {
public:
static size_t f(std::string const& s_)
{
std::cout << "B::f()" << std::endl;
return s_.size();
}
};
class C {
};
template <typename T>
class X {
public:
static
void
f(...)
{
std::cout << "Operation not supported!" << std::endl;
}
template <typename... Args>
static
std::result_of_t<decltype(&T::f)(Args...)>
f(Args&&... args_)
{
return T::f(std::forward<Args>(args_)...);
}
};
int main()
{
X<A>::f(); // Compiles, but unexpected overload!
auto const x = X<B>::f("Hello"); // Works just fine
std::cout << x << std::endl;
// X<C>::f(); // Does NOT compile!
return 0;
}
However, I've got multiple problems with it (as marked above in the comments):
If I allow the function to be not present and uncomment the line
using C as a template parameter, the code does not compile, it
still expects C to have a function f:
In instantiation of ‘class X<C>’:
required from here
error: ‘f’ is not a member of ‘C’
std::result_of_t<decltype(&T::f)(Args...)>
Based on this, I expected the ellipsis parameter do the trick.
On the other hand, even if this worked, I would have another problem: though A provides f, the overload with
the ellipsis parameter is picked during overload resolution - where, of course, f provided by A is preferred.
(Used compiler: g++ (Ubuntu 8.1.0-5ubuntu1~16.04) 8.1.0; used standard: C++14.)
Any help solving the above problems (preferably both, at the same time) is welcomed.
I propose the following X struct
template <typename T>
struct X
{
static void g (...)
{ std::cout << "Operation not supported!" << std::endl; }
template <typename ... As, typename U = T>
static auto g(int, As && ... as)
-> decltype( U::f(std::forward<As>(as)...) )
{ return T::f(std::forward<As>(as)...); }
template <typename ... As>
static auto f (As && ... as)
{ return g(0, std::forward<As>(as)...); }
};
The points are
(1) Passing through a g() function with an additional unused parameter (int in the variadic version, adsorbed by ... in the "not supported version"), permit to select the variadic version (called with 0 that is an int), when sizeof...(As) == 0 and both g() functions are available. This because int is a better match (in an exact match) for int as .... But when the variadic version isn't available (see point (2)), the "non supported" version is still available (the only one available) and selected
(2) You have to SFINAE enable/disable the variadic version according the fact that the f() static method is (or ins't) available in T. Unfortunately T is a template argument of the class, not of the g() static method, so you can use it for SFINAE. The trick is SFINAE operate over an additional template parameter, U, that is defaulted to T
// ------------------------VVVVVVVVVVVVVV additional U (defaulted to T) template type
template <typename ... As, typename U = T>
static auto g(int, As && ... as)
-> decltype( U::f(std::forward<As>(as)...) )
// -------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// SFINAE enable this g() if (and only if) the call
// U::f(std::forward<As>(as)...) is valid
The following is a simplified working example
#include <iostream>
#include <type_traits>
struct A
{ static void f() { std::cout << "A::f()" << std::endl; } };
struct B
{
static size_t f(std::string const& s_)
{ std::cout << "B::f()" << std::endl; return s_.size(); }
};
struct C
{ };
template <typename T>
struct X
{
static void g (...)
{ std::cout << "Operation not supported!" << std::endl; }
template <typename ... As, typename U = T>
static auto g(int, As && ... as)
-> decltype( U::f(std::forward<As>(as)...) )
{ return T::f(std::forward<As>(as)...); }
template <typename ... As>
static auto f (As && ... as)
{ return g(0, std::forward<As>(as)...); }
};
int main ()
{
X<A>::f(); // now call variadic version
auto const x = X<B>::f("Hello"); // Works just fine as before
std::cout << x << std::endl;
X<C>::f(); // now compile
}
Related
Is there a method to decide whether something can be constexpr evaluated, and use the result as a constexpr boolean? My simplified use case is as follows:
template <typename base>
class derived
{
template<size_t size>
void do_stuff() { (...) }
void do_stuff(size_t size) { (...) }
public:
void execute()
{
if constexpr(is_constexpr(base::get_data())
{
do_stuff<base::get_data()>();
}
else
{
do_stuff(base::get_data());
}
}
}
My target is C++2a.
I found the following reddit thread, but I'm not a big fan of the macros. https://www.reddit.com/r/cpp/comments/7c208c/is_constexpr_a_macro_that_check_if_an_expression/
Here's another solution, which is more generic (applicable to any expression, without defining a separate template each time).
This solution leverages that (1) lambda expressions can be constexpr as of C++17 (2) the type of a captureless lambda is default constructible as of C++20.
The idea is, the overload that returns true is selected when and only when Lambda{}() can appear within a template argument, which effectively requires the lambda invocation to be a constant expression.
template<class Lambda, int=(Lambda{}(), 0)>
constexpr bool is_constexpr(Lambda) { return true; }
constexpr bool is_constexpr(...) { return false; }
template <typename base>
class derived
{
// ...
void execute()
{
if constexpr(is_constexpr([]{ base::get_data(); }))
do_stuff<base::get_data()>();
else
do_stuff(base::get_data());
}
}
Not exactly what you asked (I've developer a custom type trait specific for a get_value() static method... maybe it's possible to generalize it but, at the moment, I don't know how) but I suppose you can use SFINAE and make something as follows
#include <iostream>
#include <type_traits>
template <typename T>
constexpr auto icee_helper (int)
-> decltype( std::integral_constant<decltype(T::get_data()), T::get_data()>{},
std::true_type{} );
template <typename>
constexpr auto icee_helper (long)
-> std::false_type;
template <typename T>
using isConstExprEval = decltype(icee_helper<T>(0));
template <typename base>
struct derived
{
template <std::size_t I>
void do_stuff()
{ std::cout << "constexpr case (" << I << ')' << std::endl; }
void do_stuff (std::size_t i)
{ std::cout << "not constexpr case (" << i << ')' << std::endl; }
void execute ()
{
if constexpr ( isConstExprEval<base>::value )
do_stuff<base::get_data()>();
else
do_stuff(base::get_data());
}
};
struct foo
{ static constexpr std::size_t get_data () { return 1u; } };
struct bar
{ static std::size_t get_data () { return 2u; } };
int main ()
{
derived<foo>{}.execute(); // print "constexpr case (1)"
derived<bar>{}.execute(); // print "not constexpr case (2)"
}
template<auto> struct require_constant;
template<class T>
concept has_constexpr_data = requires { typename require_constant<T::get_data()>; };
This is basically what's used by std::ranges::split_view.
I want to get into more template meta-programming. I know that SFINAE stands for "substitution failure is not an error." But can someone show me a good use for SFINAE?
I like using SFINAE to check boolean conditions.
template<int I> void div(char(*)[I % 2 == 0] = 0) {
/* this is taken when I is even */
}
template<int I> void div(char(*)[I % 2 == 1] = 0) {
/* this is taken when I is odd */
}
It can be quite useful. For example, i used it to check whether an initializer list collected using operator comma is no longer than a fixed size
template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}
The list is only accepted when M is smaller than N, which means that the initializer list has not too many elements.
The syntax char(*)[C] means: Pointer to an array with element type char and size C. If C is false (0 here), then we get the invalid type char(*)[0], pointer to a zero sized array: SFINAE makes it so that the template will be ignored then.
Expressed with boost::enable_if, that looks like this
template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> const& i,
typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}
In practice, i often find the ability to check conditions a useful ability.
Heres one example (from here):
template<typename T>
class IsClassT {
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename C> static One test(int C::*);
// Will be chosen if T is anything except a class.
template<typename C> static Two test(...);
public:
enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
enum { No = !Yes };
};
When IsClassT<int>::Yes is evaluated, 0 cannot be converted to int int::* because int is not a class, so it can't have a member pointer. If SFINAE didn't exist, then you would get a compiler error, something like '0 cannot be converted to member pointer for non-class type int'. Instead, it just uses the ... form which returns Two, and thus evaluates to false, int is not a class type.
In C++11 SFINAE tests have become much prettier. Here are a few examples of common uses:
Pick a function overload depending on traits
template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
//integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
//floating point version
}
Using a so called type sink idiom you can do pretty arbitrary tests on a type like checking if it has a member and if that member is of a certain type
//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;
//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};
struct S{
int bar;
};
struct K{
};
template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
std::cout << "has bar" << std::endl;
}
void print(...){
std::cout << "no bar" << std::endl;
}
int main(){
print(S{});
print(K{});
std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}
Here is a live example: http://ideone.com/dHhyHE
I also recently wrote a whole section on SFINAE and tag dispatch in my blog (shameless plug but relevant) http://metaporky.blogspot.de/2014/08/part-7-static-dispatch-function.html
Note as of C++14 there is a std::void_t which is essentially the same as my TypeSink here.
Boost's enable_if library offers a nice clean interface for using SFINAE. One of my favorite usage examples is in the Boost.Iterator library. SFINAE is used to enable iterator type conversions.
Here's another (late) SFINAE example, based on Greg Rogers's answer:
template<typename T>
class IsClassT {
template<typename C> static bool test(int C::*) {return true;}
template<typename C> static bool test(...) {return false;}
public:
static bool value;
};
template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);
In this way, you can check the value's value to see whether T is a class or not:
int main(void) {
std::cout << IsClassT<std::string>::value << std::endl; // true
std::cout << IsClassT<int>::value << std::endl; // false
return 0;
}
Examples provided by other answers seems to me more complicated than needed.
Here is the slightly easier to understand example from cppreference :
#include <iostream>
// this overload is always in the set of overloads
// ellipsis parameter has the lowest ranking for overload resolution
void test(...)
{
std::cout << "Catch-all overload called\n";
}
// this overload is added to the set of overloads if
// C is a reference-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)(c.*f)(), void())
{
std::cout << "Reference overload called\n";
}
// this overload is added to the set of overloads if
// C is a pointer-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)((c->*f)()), void())
{
std::cout << "Pointer overload called\n";
}
struct X { void f() {} };
int main(){
X x;
test( x, &X::f);
test(&x, &X::f);
test(42, 1337);
}
Output:
Reference overload called
Pointer overload called
Catch-all overload called
As you can see, in the third call of test, substitution fails without errors.
C++17 will probably provide a generic means to query for features. See N4502 for details, but as a self-contained example consider the following.
This part is the constant part, put it in a header.
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
The following example, taken from N4502, shows the usage:
// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())
// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;
Compared to the other implementations, this one is fairly simple: a reduced set of tools (void_t and detect) suffices. Besides, it was reported (see N4502) that it is measurably more efficient (compile-time and compiler memory consumption) than previous approaches.
Here is a live example, which includes portability tweaks for GCC pre 5.1.
Here is one good article of SFINAE: An introduction to C++'s SFINAE concept: compile-time introspection of a class member.
Summary it as following:
/*
The compiler will try this overload since it's less generic than the variadic.
T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
It simply tries the next overload.
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }
// The sink-hole.
void f(...) { }
f(1); // Calls void f(...) { }
template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.
template<class T> // A specialisation used if the expression is true.
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.
template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
return obj.serialize();
}
template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
return to_string(obj);
}
declval is an utility that gives you a "fake reference" to an object of a type that couldn't be easily construct. declval is really handy for our SFINAE constructions.
struct Default {
int foo() const {return 1;}
};
struct NonDefault {
NonDefault(const NonDefault&) {}
int foo() const {return 1;}
};
int main()
{
decltype(Default().foo()) n1 = 1; // int n1
// decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
std::cout << "n2 = " << n2 << '\n';
}
The following code uses SFINAE to let compiler select an overload based on whether a type has certain method or not:
#include <iostream>
template<typename T>
void do_something(const T& value, decltype(value.get_int()) = 0) {
std::cout << "Int: " << value.get_int() << std::endl;
}
template<typename T>
void do_something(const T& value, decltype(value.get_float()) = 0) {
std::cout << "Float: " << value.get_float() << std::endl;
}
struct FloatItem {
float get_float() const {
return 1.0f;
}
};
struct IntItem {
int get_int() const {
return -1;
}
};
struct UniversalItem : public IntItem, public FloatItem {};
int main() {
do_something(FloatItem{});
do_something(IntItem{});
// the following fails because template substitution
// leads to ambiguity
// do_something(UniversalItem{});
return 0;
}
Output:
Float: 1
Int: -1
Here, I am using template function overloading (not directly SFINAE) to determine whether a pointer is a function or member class pointer: (Is possible to fix the iostream cout/cerr member function pointers being printed as 1 or true?)
https://godbolt.org/z/c2NmzR
#include<iostream>
template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
return true;
}
template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
return true;
}
template<typename... Args>
constexpr bool is_function_pointer(Args...) {
return false;
}
struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}
int main(void) {
int* var;
std::cout << std::boolalpha;
std::cout << "0. " << is_function_pointer(var) << std::endl;
std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
return 0;
}
Prints
0. false
1. true
2. true
3. true
4. true
As the code is, it could (depending on the compiler "good" will) generate a run time call to a function which will return true or false. If you would like to force the is_function_pointer(var) to evaluate at compile type (no function calls performed at run time), you can use the constexpr variable trick:
constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;
By the C++ standard, all constexpr variables are guaranteed to be evaluated at compile time (Computing length of a C string at compile time. Is this really a constexpr?).
Consider the following code:
auto f() -> decltype(auto) { /* do whatever you want here */ }
int main() { f(); }
The return type is deduced and decltype(auto) is used as trailing return type.
The code below is a slightly modified (actually, sfinae'd) version:
struct S { static void f() {} };
struct T {};
template<typename U>
auto f(int) -> decltype(U::f(), void()) {
// do whatever you want here
}
template<typename>
auto f(char) -> decltype(auto) {
// do whatever you want here
}
int main() {
f<S>(0);
f<T>(0);
}
If you take in exam this function:
template<typename U>
auto f(int) -> decltype(U::f(), void()) {
// do whatever you want here
}
The question is: is it possible to use the trailing return type to do sfinae and still have the return type deduced?
I mean something like the code below (that doesn't work, of course):
template<typename U>
auto f(int) -> decltype(U::f(), auto) {
// do whatever you want here
}
Note: I'm not looking for alternative approaches involving template parameters, I know them and I'm just curious to know if this is a viable solution.
decltype(auto) is an inseparable construct (almost as if it were a keyword like decltype_auto). Other than that, auto cannot be used as a standalone entity inside decltype(x), because that would prevent x from being a valid expression.
You can add another parameter of type void(*)() to the function and assign a lambda with trailing return type to it as a default argument so SFINAE can be applied via lambda:
template<typename U>
decltype(auto) f(int, void(*)() = []()->decltype(U::f(), void()) {})
{
// do whatever you want here
}
Not an answer, but a possible workaround using void_t.
At least it's as DRY as what you want to do:
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
struct S { static int f() { return 3; } };
struct P { static int p() { return 4; } };
struct T {};
template<typename U, void_t<decltype(U::f())>* = nullptr >
auto f(int) -> decltype(auto)
{
// do whatever you want here
std::cout << "f1\n";
return U::f();
}
template<typename U, void_t<decltype(U::p())>* = nullptr >
auto f(int) -> decltype(auto)
{
// do whatever you want here
std::cout << "f3\n";
return U::p();
}
template<typename>
auto f(char) -> decltype(auto) {
std::cout << "f2\n";
// do whatever you want here
}
int main() {
std::cout << f<S>(0) << '\n';
std::cout << f<P>(0) << '\n';
f<T>(0);
}
Consider the following code:
#include <utility>
#include <iostream>
struct S {
template<typename T, typename... A>
auto f(A&&... args) -> decltype(std::declval<T>().f(std::forward<A>(args)...), void()) {
std::cout << "has f(int)" << std::endl;
}
template<typename>
void f(...) {
std::cout << "has not f(int)" << std::endl;
}
};
struct T { void f(int) { } };
struct U { };
int main() {
S s;
s.f<T>(42); // -> has f(int)
s.f<U>(42); // -> has not f(int)
// oops
s.f<T>(); // -> has not f(int)
}
As shown in the example the third call to f works just fine, even if the number of arguments is wrong, for it's not wrong at all for the fallback function.
Is there a way to force the number of arguments when an ellipsis is involved that way?
I mean, can I check at compile time that the size of the arguments list is exactly 1, no matter if the main function or the fallback is chosen?
Good solutions are also the ones that only involves the first template function and result in hard-errors instead of soft-errors because of the size of the parameter pack.
Of course, it can be solved with several techniques without using variadic arguments. As an example: int/char dispatching on internal template methods; explicitly specify the arguments list; whatever...
The question is not about alternative approaches to do that, I already know them.
It's just to know if I'm missing something basic here or it's not possible and that's all.
If I understand correctly your issue, you may add a layer:
struct S {
private:
template<typename T, typename... A>
auto f_impl(A&&... args)
-> decltype(std::declval<T>().f(std::forward<A>(args)...), void()) {
std::cout << "has f(int)" << std::endl;
}
template<typename>
void f_impl(...) {
std::cout << "has not f(int)" << std::endl;
}
public:
template<typename T, typename A>
auto f(A&& args) { return f_impl<T>(std::forward<A>(arg)); }
};
With traits, you may do
template <typename T, typename ... Ts>
using f_t = decltype(std::declval<T>().f(std::declval<Ts>()...));
template <typename T, typename ... Ts>
using has_f = is_detected<f_t, T, Ts...>;
struct S {
template<typename T, typename... A>
std::enable_if_t<has_f<T, A&&...>::value && sizeof...(A) == 1> f(A&&... args)
{
std::cout << "has f(int)" << std::endl;
}
template<typename T, typename... A>
std::enable_if_t<!has_f<T, A&&...>::value && sizeof...(A) == 1> f(A&&... args) {
std::cout << "has not f(int)" << std::endl;
}
};
Demo
You can use a function (assert) that gets pointer to a function to deduce size of paramemters :
#include <utility>
#include <iostream>
template <typename...Args>
struct size_assert{
template <typename T,typename R,typename... Params>
constexpr static bool assert(R(T::*)(Params...) )
{
static_assert(sizeof...(Args) == sizeof...(Params),"Incorrect size of arguments!");
return true;
}
};
struct S {
template<typename T, typename... A, bool = size_assert<A...>::assert(&T::f)>
auto f(A&&... args) -> decltype(std::declval<T>().f(std::forward<A>(args)...), void())
{
std::cout << "has f(int)" << std::endl;
}
template<typename>
void f(...) {
std::cout << "has not f(int)" << std::endl;
}
};
struct T { void f(int) { } };
struct U { };
int main() {
// std::cout <<fc(&f);
S s;
s.f<T>(42); // -> has f(int)
s.f<U>(42); // -> has not f(int)
// oops
s.f<T>(); // -> has not f(int)
}
I have a function definition like so
template <typename T>
auto print(T t) -> decltype(t.print()) {
return t.print();
}
The idea is that the argument must be of type T and must have the print function. This print function could return anything, explaining the need of decltype. So for example you can do:
struct Foo
{
int print()
{
return 42;
}
};
struct Bar
{
std::string print()
{
return "The answer...";
}
};
...
std::cout << print(Foo()) << std::endl;
std::cout << print(Bar()) << std::endl;
/* outputs:
42
The answer...
*/
I read that templates cannot do runtime instantiation and that you can have the classes derive from a base class, then determine their types to see what template argument to use. However, how would I do this for a non-class type? The idea is to be able to have:
template <typename T>
T print(T t) {
return t;
}
as well, but this gives me ambiguous overload errors. Qualifying doesn't work, ie print<Foo>. And the other got'cha is, what if I had a functor like:
struct Foo
{
virtual int print();
operator int() const
{
return 42;
}
};
How does it decide now?
So my question is, is it possible to resolve all these ambiguities with templates, or do I have to write a bunch of redundant code?
Tests
I incrementally added tests, copy/pasting each edit'ed solution from below. Here are the results:
With the following classes:
struct Foo
{
int print()
{
return 42;
}
operator int() const
{
return 32;
}
};
struct Bar
{
std::string print()
{
return "The answer...";
}
operator int() const
{
return (int)Foo();
}
};
struct Baz
{
operator std::string() const
{
return std::string("The answer...");
}
};
And the following test output:
std::cout << print(Foo()) << std::endl;
std::cout << print(Bar()) << std::endl;
std::cout << print(42) << std::endl;
std::cout << print((int)Foo()) << std::endl;
std::cout << print("The answer...") << std::endl;
std::cout << print(std::string("The answer...")) << std::endl;
std::cout << print((int)Bar()) << std::endl;
std::cout << print((std::string)Baz()) << std::endl;
Both correctly output:
42
The answer...
42
32
The answer...
The answer...
32
The answer...
You could adopt the following approach, which invokes a print() member function on the input if such a member function exists, otherwise it will return the input itself:
namespace detail
{
template<typename T, typename = void>
struct print_helper
{
static T print(T t) {
return t;
}
};
template<typename T>
struct print_helper<T, decltype(std::declval<T>().print(), (void)0)>
{
static auto print(T t) -> decltype(t.print()) {
return t.print();
}
};
}
template<typename T>
auto print(T t) -> decltype(detail::print_helper<T>::print(t))
{
return detail::print_helper<T>::print(t);
}
Here is a live example.
Simple solution using manual overloading for each type which you want to print directly:
Define your first implementation which calls T::print(). Use overloading to specify alternative implementations for all types which don't have this function. I don't recommend this solution, but it's very easy to understand.
template<typename T>
auto print(T t) -> decltype(t.print()) {
return t.print();
}
int print(int t) {
return t;
}
std::string print(std::string t) {
return t;
}
// ... and so on, for each type you want to support ...
More advanced solution using SFINAE which uses T::print() automatically if and only if it's there:
First, define a trait which can decide if your type has a function print(). Basically, this trait inherits from either std::true_type or std::false_type, depending on the decision being made in some helper class (_test_print). Then, use this type trait in an enable_if compile-time decision which defines only one of the two cases and hides the other one (so this is not overloading).
// Type trait "has_print" which checks if T::print() is available:
struct _test_print {
template<class T> static auto test(T* p) -> decltype(p->print(), std::true_type());
template<class> static auto test(...) -> std::false_type;
};
template<class T> struct has_print : public decltype(_test_print::test<T>(0)) {};
// Definition of print(T) if T has T::print():
template<typename T>
auto print(T t) -> typename std::enable_if<has_print<T>::value, decltype(t.print())>::type {
return t.print();
}
// Definition of print(T) if T doesn't have T::print():
template<typename T>
auto print(T t) -> typename std::enable_if<!has_print<T>::value, T>::type {
return t;
}
Have a look at the live demo.