5

Can a function template be enabled whenever some expression is undefined (e.g., xof type t is not streamable to std::cout). Something like

template<typename t>
auto f(const t &x) 
  -> typename enable_if_undefined<decltype(std::cout << x)>::type;

Using SFINAE, I only see how to enable if the expression is defined, but not how to do it if the expression is undefined.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
Xlea
  • 495
  • 1
  • 4
  • 14

2 Answers2

7

You need a helper which provides a boolean value you can reverse:

template<typename, typename=void>
struct has_cout
    : std::false_type {};

template<typename T>
struct has_cout<T, decltype(std::cout << std::declval<T>(),void())>
    : std::true_type {};

template<typename T>
auto f(const T& x) 
  -> typename std::enable_if<!has_cout<T>::value>::type;

Live example

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • I actually want a slight generalization where the stream type is a template argument. Canonical for me would be to add it as another type in front. This fails, however, as streamable objects are now detected as non-streamable, see [here](http://coliru.stacked-crooked.com/a/6765c6a4b6a21f3f). I suspected that the rvalue-ref returned by `std::declval()` might cause the problem, but then `std::move(std::cout) << 0` is valid. Can you please also briefly explain what the `,void()` in the specialization of `has_cout` does? – Xlea May 07 '15 at 21:01
  • 1
    @Xlea And for the second question about `,void()`: It makes sure that the `decltype` evaluates to `void`, [this answer](http://stackoverflow.com/a/16044573/2073257) has more information. – Daniel Frey May 07 '15 at 22:25
  • 1
    @Xlea ou forgot the `stream_t` in the parameter list for the specialization and you need to add `&` to `declval()` to get a non-const reference. See [here](http://coliru.stacked-crooked.com/a/d27f10c7059a7480). – Daniel Frey May 07 '15 at 22:30
  • Stupid error, forgetting `stream_t` in the parameter list. Thanks a lot for your patience :) – Xlea May 08 '15 at 07:01
  • Your ostream-generic-solution does not work for Visual C++ 2013. If I take your example and activate the line that shouldn't compile (and which it doesn't in g++), it actually does (with VC++): [here is the complete code](http://coliru.stacked-crooked.com/a/8ec3ad4a4d8bca31). Any ideas what is going wrong? [Update: Same problem with the original example. Strangely, Intellisense actually "senses" an error at the line, but the compiler does not complain.] – Xlea May 22 '15 at 08:34
  • @Xlea I don't know, I don't have Windows or VS. Maybe try replacing the `decltype(..., void())` with `typename std::enable_if::type`, but I really have no idea how to tame VS... – Daniel Frey May 22 '15 at 08:42
  • Unfortunately no, I'll try to figure it out when I have time. – Xlea May 22 '15 at 08:54
3
template<class...>struct types{using type=types;};
namespace details {
  template<template<class...>class Z, class types, class=void>
  struct test_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct test_apply<Z,types<Ts...>,std::void_t<Z<Ts...>>>:
    std::true_type
  {};
};
template<template<class...>class Z, class...Ts>
using test_apply=details::test_apply<Z,types<Ts...>>;

is the metaprogramming boilerplate.

Then the actual use-case specific code is really clean. First, a simple decltype alias:

template<class X>
using cout_result = decltype( std::cout << std::declval<X>() );

and then we apply the test to it:

template<class X>
using can_cout_stream = test_apply< cout_result, X >;

the goal here is to decouple the metaprogramming boilerplate from the actual use.

template<class T>
std::enable_if_t<!can_cout_stream<const T&>{}>
f(const T& x) 

I liberally use C++14 features to make things a touch cleaner. They can all be easily (re)implemented in C++11 if your compiler doesn't have them.

template<bool b, class T=void>
using enable_if_t=typename std::enable_if<b,T>::type;
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

should cover it.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • This is Walter Brown's ["detection idiom"](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4436.pdf), right? – T.C. May 05 '15 at 21:13
  • @T.C. looks like. My `test_apply` is close to his `is_detected_v`, almost. I use `true_type` and `false_type` (well, types deriving from same), he uses `true` and `false`. I hadn't seen that paper. The `_t` thing where it returns some default type (like `void`) on failure seems strange to me. Maybe it is useful? – Yakk - Adam Nevraumont May 05 '15 at 21:21
  • The intention seems to be to allow you to check both the well-formedness of the thing and the type of the result at the same time. (Edit: ah, it's there for things like "`Alloc::pointer` if such a type exists; otherwise, `value_type*`".) – T.C. May 05 '15 at 21:48
  • I do `apply_unless`, but I could see the utility in folding them together. – Yakk - Adam Nevraumont May 06 '15 at 01:46
  • Thanks for the answer. It certainly looks interesting, but I failed to get your suggestion working, see [here](http://coliru.stacked-crooked.com/a/afb841a382454aac). What did I do wrongly? – Xlea May 07 '15 at 20:53
  • 1
    @xlea `std::void_t` is C++1z. `templatestruct voider{using type=void;};templateusinf void_t=typename voider::type;` should replace it. Maybe other issues, on phone, be back later. – Yakk - Adam Nevraumont May 07 '15 at 23:44
  • That's it, it [works](http://coliru.stacked-crooked.com/a/dafe2137b6f00a26). (I thought it was C++14, which is enabled in the above link.) Can you also hint on how to generalize so that the stream type can be passed as a template argument. – Xlea May 08 '15 at 07:38
  • @xlea `cout_result` takes another parameter, does `declval()` on it. `can_cout_stream` takes an extra parameter, passes it on. `enable_if` clause modified. Function takes another template parameter, passes it into `enavle_if` clause to the `can_...` clause? – Yakk - Adam Nevraumont May 08 '15 at 10:37