Search code examples
c++c++14sfinaestatic-assert

Detect idiom with function failing static_assert


Is there a way to use the detection idiom (or another method) to test whether a function is valid for given template arguments, if it fails due to a static_assert?

The example below illustrates that validity of foo (failing return type computation) is detected as intended, but that of bar (failing static_assert) is not.

#include <iostream>
#include <type_traits>

template <typename... T> using void_t = void;

template <class AlwaysVoid, template<class...> class Op, class... Args>
struct detector: std::false_type { };

template <template<class...> class Op, class... Args>
struct detector<void_t<Op<Args...>>, Op, Args...>: std::true_type { };

template <template<class...> class Op, class... Args>
constexpr bool is_detected = detector<void, Op, Args...>::value;

template <typename T>
std::enable_if_t<!std::is_void<T>::value> foo() {
  std::cout << "foo" << std::endl;
}

template <typename T>
void bar() {
  static_assert( !std::is_void<T>::value );
  std::cout << "bar" << std::endl;
}

template <typename T> using foo_t = decltype(foo<T>());
template <typename T> using bar_t = decltype(bar<T>());

int main(int argc, char* argv[]) {

  foo<int>();
  // foo<void>(); // fails as expected

  bar<int>();
  // bar<void>(); // fails as expected

  std::cout << std::boolalpha;

  // detection works for foo
  std::cout << is_detected<foo_t,int > << std::endl; // true
  std::cout << is_detected<foo_t,void> << std::endl; // false

  // but not for bar
  std::cout << is_detected<bar_t,int > << std::endl; // true
  std::cout << is_detected<bar_t,void> << std::endl; // true !!!
}

This is the reason I can't detect if a boost::lexical_cast is valid for given types.


Solution

  • It's not possible to use SFINAE to get the proper output here, because SFINAE rules operate on declarations, not definitions.

    The type of bar as declared will always be void(void), so the declaration is okay as far as SFINAE is concerned.

    If you write up a real detection idiom (Like I did here), and use it like so:

    template <typename T> 
    using CanCallFoo_t = decltype(&foo<T>);
    
    template<class T>
    using CanCallFoo = detect<T, CanCallFoo_t, void>;
    
    template<class T>
    using CanCallBar_t = decltype(&bar<T>);
    
    template< class T>
    using
    CanCallBar = detect<T, CanCallBar_t, void>;
    
    //...
    std::cout << CanCallFoo<int>::value << std::endl; // true
    std::cout << CanCallFoo<void>::value << std::endl; // false
    
    std::cout << CanCallBar<int>::value << std::endl;
    std::cout << CanCallBar<void>::value << std::endl;
    

    You'll notice that SFINAE succeeds and then you get a compiler error when the definition is parsed.

    error: static assertion failed
    static_assert( !std::is_void<T>::value );

    Demo

    Notice that it works with foo because foo's declared type will fail SFINAE for void

    The point of static_assert is to make compilation fail if no other better match is found, not as a substitution for SFINAE.