SFINAE fallback when a function does not exist

Viewed 446

I'm currently trying to implement a toString function which calls .toString() or std::to_string() depending on which is available for the deduced type

So far I have this working snippet:

#include <iostream>
#include <string>

template <class T>
auto toString(const T& obj)
        -> decltype(obj.toString(), std::string())
{
  return obj.toString();
}

template <class T>
auto toString(const T& obj)
        -> decltype(std::to_string(obj), std::string())
{
  return std::to_string(obj);
}

template <class T>
auto toString(const T& obj)
        -> decltype(std::string(obj))
{
  return std::string(obj);
}

class Foo{
public:
  std::string toString() const {
    return "Hello";
  }
};

int main()
{
  Foo bar;
  std::cout << toString(bar);
  std::cout << toString(5);
  std::cout << toString("Hello const char*");
}

Now I want to insert a static_assert when no overload from those above is viable, because the default GCC error message for older GCC versions is not very informative.

How can I check if neither .toString() nor std::to_string() are possible for T?

So far I found no way to check if something is not present, only the other way around. I hope someone has an idea how to solve this and thanks for your time.

3 Answers

You need to introduce an overload that is worse than every other that you currently have, and delete it. You don't need to check if neither of the string functions exist that way.

A popular way to do this is to use C style variadic arguments:

std::string toString(...) = delete;

You can also use static_assert with a custom error message:

class Dummy
{
public:
    std::string toString() const;    
private:
    Dummy() = default;
};

template <typename... Ts>
auto toString(Ts...)
{
    static_assert(std::is_same<std::tuple<Ts...>, std::tuple<Dummy>>::value, "neither std::to_str nor member toString() exists");
    return "";
}

live example

namespace details{
  template<template<class...> class, 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{};
}
template<template<class...> class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

template<class T>
using dot_toString_r = decltype(std::declval<T>().toString());

template<class T>
using can_dot_toString = can_apply<dot_toString_r, T>;

I leave can_std_to_string as an exercise.

If you lack void_t in your standard revision:

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

Which works even in early compilers.

Related