Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Metaprograming: Failure of Function Definition Defines a Separate Function

In this answer I define a template based on the type's is_arithmetic property:

template<typename T> enable_if_t<is_arithmetic<T>::value, string> stringify(T t){
    return to_string(t);
}
template<typename T> enable_if_t<!is_arithmetic<T>::value, string> stringify(T t){
    return static_cast<ostringstream&>(ostringstream() << t).str();
}

dyp suggests that rather than the is_arithmetic property of the type, that whether to_string is defined for the type be the template selection criteria. This is clearly desirable, but I don't know a way to say:

If std::to_string is not defined then use the ostringstream overload.

Declaring the to_string criteria is simple:

template<typename T> decltype(to_string(T{})) stringify(T t){
    return to_string(t);
}

It's the opposite of that criteria that I can't figure out how to construct. This obviously doesn't work, but hopefully it conveys what I'm trying to construct:

template<typename T> enable_if_t<!decltype(to_string(T{})::value, string> (T t){
    return static_cast<ostringstream&>(ostringstream() << t).str();
}
like image 636
Jonathan Mee Avatar asked May 12 '15 11:05

Jonathan Mee


4 Answers

Using Walter Brown's void_t:

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

It's very easy to make such a type trait:

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

template<typename T>
struct has_to_string<T, 
    void_t<decltype(std::to_string(std::declval<T>()))>
    > 
: std::true_type { };
like image 75
Barry Avatar answered Oct 07 '22 11:10

Barry


First, I think SFINAE should usually be hidden from interfaces. It makes the interface messy. Put the SFINAE away from the surface, and use tag dispatching to pick an overload.

Second, I even hide SFINAE from the traits class. Writing "can I do X" code is common enough in my experience that I don't want to have to write messy SFINAE code to do it. So instead I write a generic can_apply trait, and have a trait that SFINAE fails if passed the wrong types using decltype.

We then feed the SFIANE failing decltype trait to can_apply, and get out a true/false type depending on if the application fails.

This reduces the work per "can I do X" trait to a minimal amount, and places the somewhat tricky and fragile SFINAE code away from day-to-day work.

I use C++1z's void_t. Implementing it yourself is easy (at the bottom of this answer).

A metafunction similar to can_apply is being proposed for standardization in C++1z, but it isn't as stable as void_t is, so I'm not using it.

First, a details namespace to hide the implementation of can_apply from being found by accident:

namespace details {
  template<template<class...>class Z, class, class...>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
    std::true_type{};
}

We can then write can_apply in terms of details::can_apply, and it has a nicer interface (it doesn't require the extra void being passed):

template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;

The above is generic helper metaprogramming code. Once we have it in place, we can write a can_to_string traits class very cleanly:

template<class T>
using to_string_t = decltype( std::to_string( std::declval<T>() ) );

template<class T>
using can_to_string = can_apply< to_string_t, T >;

and we have a trait can_to_string<T> that is true iff we can to_string a T.

The work require to write a new trait like that is now 2-4 lines of simple code -- just make a decltype using alias, and then do a can_apply test on it.

Once we have that, we use tag dispatching to the proper implementation:

template<typename T>
std::string stringify(T t, std::true_type /*can to string*/){
  return std::to_string(t);
}
template<typename T>
std::string stringify(T t, std::false_type /*cannot to string*/){
  return static_cast<ostringstream&>(ostringstream() << t).str();
}
template<typename T>
std::string stringify(T t){
  return stringify(t, can_to_string<T>{});
}

All of the ugly code is hiding in the details namespace.

If you need a void_t, use this:

template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

which works in most major C++11 compilers.

Note that the simpler template<class...>using void_t=void; fails to work in some older C++11 compilers (there was an ambiguity in the standard).

like image 40
Yakk - Adam Nevraumont Avatar answered Oct 07 '22 11:10

Yakk - Adam Nevraumont


Freshly voted into the library fundamentals TS at last week's committee meeting:

template<class T>
using to_string_t = decltype(std::to_string(std::declval<T>()));

template<class T>
using has_to_string = std::experimental::is_detected<to_string_t, T>;

Then tag dispatch and/or SFINAE on has_to_string to your heart's content.

You can consult the current working draft of the TS on how is_detected and friends can be implemented. It's rather similar to can_apply in @Yakk's answer.

like image 15
T.C. Avatar answered Oct 07 '22 10:10

T.C.


You could write a helper trait for this using expression SFINAE:

namespace detail
{
    //base case, to_string is invalid
    template <typename T>
    auto has_to_string_helper (...) //... to disambiguate call
       -> false_type;

    //true case, to_string valid for T
    template <typename T>
    auto has_to_string_helper (int) //int to disambiguate call
       -> decltype(std::to_string(std::declval<T>()), true_type{});
}

//alias to make it nice to use
template <typename T>
using has_to_string = decltype(detail::has_to_string_helper<T>(0));

Then use std::enable_if_t<has_to_string<T>::value>

Demo

like image 10
TartanLlama Avatar answered Oct 07 '22 11:10

TartanLlama