Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 865
SU3 Avatar asked Aug 22 '17 02:08

SU3


2 Answers

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.

like image 198
AndyG Avatar answered Nov 07 '22 02:11

AndyG


Andy's answer is correct, in explaining that detecting a static_assert or any definition substitution failure is not possible with sfinae. I wanted to point out though, that it is possible to solve your problem, though you will need to bear the brunt of some repetition.

Basically, you need to find out what kinds of operations lexical_cast is trying to apply to a generic type. Then you wrap lexical_cast with your own function, and sfinae your function on whatever properties lexical_cast requires. This isn't elegant but I doubt there are more than a few requirements on the type relevant to your application of interest, so it is a practical solution (perhaps).

Maybe this is obvious but I thought I would mention this since it's not yet covered.

like image 1
Nir Friedman Avatar answered Nov 07 '22 02:11

Nir Friedman