Search code examples
c++sfinaeenable-if

How to force template substitution failure unless T::answer is in integral type?


I have the following code:

#include <type_traits>

struct SA {};
struct SB { static const int answer = 42; };
const int SB::answer;

template <typename T>
int F() {
  return T::answer;
}

int main(int argc, char **argv) {
  (void)argc; (void)argv;
  // F<SA>();  // I want to make this a template substitution failure.
  return F<SB>();  // This should still work.
}

I want to make the call F<SA>() become a template substitution failure. I've tried changing int F to typename std::enable_if<std::is_integral<T::answer>::value, int>::type F, but I got the following error:

$ g++ --versiong++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
$ g++ -W -Wall -Wextra -Werror -std=c++0x -fno-diagnostics-show-caret ei.cc && ./a.out
ei.cc:8:55: error: to refer to a type member of a template parameter, use ‘typename T:: answer’ [-fpermissive]
ei.cc: In function ‘int main(int, char**)’:
ei.cc:16:20: error: no matching function for call to ‘F()’
ei.cc:16:20: note: candidate is:
ei.cc:8:76: note: template<class T> typename std::enable_if<std::is_integral<typename T::answer>::value, int>::type F()
ei.cc:8:76: note:   template argument deduction/substitution failed:
ei.cc: In substitution of ‘template<class T> typename std::enable_if<std::is_integral<typename T::answer>::value, int>::type F() [with T = SB]’:
ei.cc:16:20:   required from here
ei.cc:8:76: error: no type named ‘answer’ in ‘struct SB’

This is trying to look for a type named answer in struct SB, but I want to make it look for an integral field named answer in struct SB.


Solution

  • I find putting the enable_if check in the template arguments makes your interface a bit clearer:

    template <typename T,
              std::enable_if_t<std::is_integral<decltype(T::answer)>::value>* = nullptr>
    int F() {
      return T::answer;
    }
    

    Or even clearer, using R. Martinho Fernandes' Remastered enable_if.

    namespace detail {
        enum class enabler {};
    }
    template <typename Condition>
    using EnableIf = std::enable_if_t<Condition::value, detail::enabler>;
    
    template <typename T, EnableIf<std::is_integral<decltype(T::answer)>>...>
    int F() {
      return T::answer;
    }