Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE: detect existence of a template function that requires explicit specialization

As a follow-up to my previous question, I am trying to detect the existence of a template function that requires explicit specialization.

My current working code detects non-template functions (thanks to DyP's help), provided they take at least one parameter so that dependent name lookup can be used:

// switch to 0 to test the other case
#define ENABLE_FOO_BAR 1

namespace foo {
  #if ENABLE_FOO_BAR
    int bar(int);
  #endif
}

namespace feature_test {
  namespace detail {
    using namespace foo;
    template<typename T> decltype(bar(std::declval<T>())) test(int);
    template<typename> void test(...);
  }
  static constexpr bool has_foo_bar = std::is_same<decltype(detail::test<int>(0)), int>::value;
  static_assert(has_foo_bar == ENABLE_FOO_BAR, "something went wrong");
}

(the ENABLE_FOO_BAR macro is just for testing purpose, in my real code I don't have such a macro available otherwise I wouldn't be using SFINAE)

This also works perfectly with template functions when their template arguments can automatically be deduced by the compiler:

namespace foo {
  #if ENABLE_FOO_BAR
    template<typename T> int bar(T);
  #endif
}

However when I try to detect a template function that requires explicit specialization, the static_assert kicks in when foo::bar() exists:

namespace foo {
  #if ENABLE_FOO_BAR
    template<typename T, typename U> T bar(U);
  #endif
}

//...
// error: static assertion failed: something went wrong

Obviously the compiler can't deduce the template arguments of bar() so the detection fails. I tried to fix it by explicitly specializing the call:

template<typename T> decltype(bar<int, T>(std::declval<T>())) test(int);
//      explicit specialization  ^^^^^^^^

This works fine when foo::bar() exists (the function is correctly detected) but now all hell breaks loose when foo::bar() doesn't exist:

error: ‘bar’ was not declared in this scope
     template<typename T> decltype(bar<int, T>(std::declval<T>())) test(int);
                                   ^
error: expected primary-expression before ‘int’
     template<typename T> decltype(bar<int, T>(std::declval<T>())) test(int);
                                       ^
// lots of meaningless errors that derive from the first two

It seems my attempt at explicit specialization failed because the compiler doesn't know that bar is a template.

I'll spare you everything I tried to fix this and get straight to the point: how can I detect the existence of a function such as template<typename T, typename U> T bar(U); that requires explicit specialization in order to be instantiated?

like image 927
syam Avatar asked Sep 21 '13 18:09

syam


People also ask

What is SFINAE used for?

One of the primary uses of SFINAE can be found through enable_if expressions. enable_if is a set of tools, available in the Standard Library since C++11, that internally use SFINAE. They allow to include or exclude overloads from possible function templates or class template specialization.

When we Specialise of function template it is called?

The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.

Will concepts replace SFINAE?

So the simple answer is YES.

What is the use of template specialization?

This is called template specialization. Template allows us to define generic classes and generic functions and thus provide support for generic programming. Generic programming is an approach where generic data types are used as parameters in algorithms so that they work for variety of suitable data types.


1 Answers

Following may help you:

// Helper macro to create traits to check if function exist.
// Note: template funcName should exist, see below for a work around.
#define HAS_TEMPLATED_FUNC(traitsName, funcName, Prototype)                          \
    template<typename U>                                                             \
    class traitsName                                                                 \
    {                                                                                \
        typedef std::uint8_t yes;                                                    \
        typedef std::uint16_t no;                                                    \
        template <typename T, T> struct type_check;                                  \
        template <typename T = U> static yes &chk(type_check<Prototype, &funcName>*); \
        template <typename > static no &chk(...);                                    \
    public:                                                                          \
        static bool const value = sizeof(chk<U>(0)) == sizeof(yes);                  \
    }

So with provided namespace with bar and without bar2

// namespace to test
namespace foo {
    template<typename T, typename U> T bar(U);
    // bar2 not present
}

Code which check the presence of bar<int, int> and bar2<int, int>.

// dummy class which should be never used
namespace detail {
    struct dummy;
}

// Trick, so the names exist.
// we use a specialization which should never happen
namespace foo {
    template <typename T, typename U>
    std::enable_if<std::is_same<detail::dummy, T>::value, T> bar(U);

    template <typename T, typename U>
    std::enable_if<std::is_same<detail::dummy, T>::value, T> bar2(U);
}

#define COMMA_ , // trick to be able to use ',' in macro

// Create the traits
HAS_TEMPLATED_FUNC(has_foo_bar, foo::bar<T COMMA_ int>, int(*)(int));
HAS_TEMPLATED_FUNC(has_foo_bar2, foo::bar2<T COMMA_ int>, int(*)(int));

// test them
static_assert(has_foo_bar<int>::value, "something went wrong");
static_assert(!has_foo_bar2<int>::value, "something went wrong");
like image 119
Jarod42 Avatar answered Oct 17 '22 16:10

Jarod42