15

I have been trying to choose between two templated functions based on whether an overload operator<<(std::ostream&, const T&) exists.

Example:

template <typename T, typename std::enable_if</* ? */, int>::type = 0>
std::string stringify(const T& t)
{
    std::stringstream ss;
    ss << t;
    return ss.str();
}

template <typename T, typename std::enable_if</* ? */, int>::type = 0>
std::string stringify(const T& t)
{
    return "No overload of operator<<";
}

struct Foo { };

int main()
{
    std::cout << stringify(11) << std::endl;
    std::cout << stringify(Foo{}) << std::endl;
}

Is this possible? And if so, how would you solve this problem?

Kyle Mayes
  • 320
  • 4
  • 12
  • The question has been marked as duplicate to a non-C++11 question. But a C++11 answer to that question can be found here among its answers: http://stackoverflow.com/a/9154394/1728537 – lrineau Jun 03 '14 at 13:45

1 Answers1

20

There's no need for enable_if, use expression SFINAE to select the correct overload when the operator<< is present.

namespace detail
{
    template<typename T>
    auto stringify(std::stringstream& ss, T const& t, bool)
        -> decltype(ss << t, void(), std::string{})
    {
        ss << t;
        return ss.str();
    }

    template<typename T>
    auto stringify(std::stringstream&, T const&, int)
        -> std::string
    {
        return "No overload of operator<<";
    }
}

template <typename T>
std::string stringify(const T& t)
{
    std::stringstream ss;
    return detail::stringify(ss, t, true);
}

Live demo

The stringify function template simply delegates to one of the detail::stringify function templates. Then, the first one is selected if the expression ss << t is well-formed. The unnamed bool parameter is being used for disambiguation between the two detail::stringify implementations. Since the primary stringify function passes true as the argument to detail::stringify, the first one will be a better match when the operator<< overload is present. Otherwise the second one will be selected.

This expression decltype(ss << t, void(), std::string{}) in the trailing return type of the first stringify template probably merits a more detailed explanation. Here we have a single expression consisting of 3 sub-expressions separated by the comma operator.

The first one, ss << t is what determines whether that function template passes template parameter substitution and will be added to the overload resolution set. This will occur if the expression is well-formed, i.e. if the type in question overloads operator<<.

The middle sub-expression, void() doesn't do anything other than ensure that some user-defined operator, is not selected (because you cannot overload operator, with a void parameter type).

The third, and rightmost, sub-expression, std::string{} is what determines the return type of the detail::stringify function.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Not to go too off topic, but I don't understand the `decltype` in your first function. Why does it have two arguments? Isn't it just going to be `std::string`? – Adam Jun 02 '14 at 06:51
  • 5
    @Adam: It doesn't have two arguments; it has a single argument, consisting of an expression with a comma operator. If `ss << t` is well-formed, then the type of the full expression is `std::string`. If it isn't, then substitution fails and the template isn't instantiated, so the second version is selected instead. – Mike Seymour Jun 02 '14 at 07:09
  • So does the `ss << t` expression have to appear in the `decltype` for SFINAE to apply? Wouldn't the `ss << t` in the body also serve the same purpose? – Adam Jun 02 '14 at 07:29
  • 2
    @Adam -- Substitution occurs in the types/expressions used in the function type (that is, its return type and parameter types) and its template parameters. So while `ss << t` doesn't necessarily need to occur in the `decltype`, the `ss << t` in the body won't serve the same purpose because then the substitution would have succeeded and would lead to a hard compilation error rather than the function being SFINAE'd out of the overload set. – mpark Jun 02 '14 at 08:54
  • Gotcha. So the `decltype` provides way to get the expression into the function declaration without resorting to more complicated methods like `enable_if`. Neat. – Adam Jun 02 '14 at 09:08
  • [Woop woop.](http://stackoverflow.com/a/9154394/500104) – Xeo Jun 02 '14 at 09:20
  • @Xeo Already upvoted that a long time ago, so I can't send any more rep along your way :) That answer was where I first learned this method of overload resolution tie-breaking, so thank you for that. – Praetorian Jun 02 '14 at 15:12
  • @praetorian -1 removed! That mistake is really, really common. Now for a +1, explain `decltype( blah, void(), std::string )` in the question for the poor sucker who hasn't seen this before! ;) – Yakk - Adam Nevraumont Jun 02 '14 at 15:15
  • @Yakk Man, you're a tough customer :) Added an explanation, and it looks like it's longer than the rest of the answer! – Praetorian Jun 02 '14 at 15:42
  • @Praetorian that is actually why I don't like this technique: getting it right, and commenting what the heck is going on, bulks it up to the point where it isn't any shorter than the alternatives! We really require `requires`... – Yakk - Adam Nevraumont Jun 02 '14 at 15:44
  • 1
    You might aswell have just linked to my answer, where I also explain all that. :P /cc @Yakk – Xeo Jun 02 '14 at 15:50
  • @Yakk "We really require `requires`" would be funnier if `requires requires` wasn't actually valid syntax in Concepts Lite. There's an example in the [current draft of the wording document](https://github.com/cplusplus/concepts-ts/blob/master/concepts.pdf): `template requires requires (T x) { x + x; } T add(T a, T b) { return a + b; }` – Casey Jun 02 '14 at 16:54
  • @Xeo Oops, I saw that question, had I just scrolled past the first two answers I would have found what I needed in your answer. Sorry for the near duplicate question. – Kyle Mayes Jun 02 '14 at 17:32
  • @KyleMayes: No problem, that's what duplicate votes are there for. Mind me closing this one as a dupe of the other? – Xeo Jun 02 '14 at 17:45
  • @Xeo I guess not, thanks. – Kyle Mayes Jun 02 '14 at 17:56