30

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();
}
Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

8 Answers8

18

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 { };
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 2
    Very elegant, +1. Do you happen to know if that has a good chance of making standardisation? – TartanLlama May 12 '15 at 12:30
  • 2
    @TartanLlama No clue. At least it's very easy to implement yourself :) – Barry May 12 '15 at 12:33
  • 4
    @TartanLlama It's in the draft of the next standard. GCC and Clang have it when compiling with `-std=c++1z` (and they have `__void_t` with `-std=c++11`) and MSVC 2015 has it. – Oktalist May 12 '15 at 13:47
  • @Oktalist Seems like an answer working with the standardized tool rather than writing our own. Could we either edit this answer or write a new one describing that? – Jonathan Mee May 12 '15 at 13:55
  • @Oktalist `__void_t` yes, not `void_t` – Barry May 12 '15 at 13:59
  • @Barry What do you mean? – Oktalist May 12 '15 at 13:59
  • @Oktalist I don't see either Clang 3.6.0 or gcc 5.1.0 having `std::void_t`. Both have `std::__void_t` though. – Barry May 12 '15 at 14:01
  • @Oktalist Oh, that's the only change? we use this answer's code but we write `using __void_t = void`? Or are you saying that is already defined? – Jonathan Mee May 12 '15 at 14:01
  • @Barry I don't know what to say. I saw it in their `` [here](https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/std/type_traits;h=b8ec61f6892a31e5d02f591b54aa9b4c04654c4d;hb=HEAD#l2408) and [here](http://llvm.org/svn/llvm-project/libcxx/trunk/include/type_traits). – Oktalist May 12 '15 at 14:11
  • 2
    @JonathanMee Change what? I don't know what you're talking about. Everything you need is in Barry's answer. You can replace `void_t` with `std::void_t` or `std::__void_t` if your standard library implements it, the result will be the same. – Oktalist May 12 '15 at 14:14
  • @Oktalist So I'd just use this code in an `enable_if_t`, like this: `template enable_if_t::value, string> stringify(T t){ return to_string(t); } template enable_if_t<!has_to_string::value, string> stringify(T t){ return static_cast(ostringstream() << t).str(); }` Hmmm... I must be misunderstanding how `__void_t` works, can you elaborate a bit? – Jonathan Mee May 12 '15 at 14:21
  • @JonathanMee: If the compiler can figure out what the type of `std::to_string(std::declval())` is, then it passes that type to `__void_t`, which is simply `void`, and the `true` specialization is used. If the compiler _can't_ figure out what `std::to_string(std::declval())` is, it abandons that specialization altogether, and selects the only remaining option: the `false`. `__void_t` does nothing but discard the return type. – Mooing Duck May 12 '15 at 17:32
  • @MooingDuck So, [Matthis Vega's answer](http://stackoverflow.com/a/30190191/2642059) doesn't use `__void_t` at all. And seems to do the exact same thing. What I'm trying to figure out is what's the benefit of using `__void_t`? – Jonathan Mee May 13 '15 at 10:44
  • 3
    @JonathanMee The benefit of using `void_t` is that it works. Matthis Vega's answer [doesn't work](http://coliru.stacked-crooked.com/a/b53e7413f82de303). – Barry May 13 '15 at 11:36
  • @Oktalist Sorry I'm taking forever to accept an answer I'm struggling to understand the code in some of these answers. For example I still think I don't understand the point of `void_t` rather than have you keep explaining it to me in comments I've asked the question here: http://stackoverflow.com/q/30302423/2642059 – Jonathan Mee May 18 '15 at 11:50
  • @JonathanMee There's no time limit for accepting an answer, and no obligation to do so ever. – Barry May 18 '15 at 14:40
17

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).

erip
  • 16,374
  • 11
  • 66
  • 121
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • What I don't like about this form of tag dispatch is that the meaning of `true_type` and `false_type` is defined only by the caller. It is not immediately obvious what is tested from just the set of dispatch targets. The `MF(Args)` form of concepts emulation has the advantage that it is lazily evaluated, as opposed to alias templates. This can be used to prevent substitution failures which don't appear in the immediate context. – dyp May 12 '15 at 16:11
  • @dyp true. Which is why I usually give it a commented-out name. Added ` /*can to string*/` in both cases. I'm uncertain what kind of errors your `MF` approach avoids in the non-immediate context that mine doesn't? Is it because of the `choice<>` overloads or something? – Yakk - Adam Nevraumont May 12 '15 at 16:20
  • No, and I'm not sure if you'd ever encounter them in tag dispatch (as opposed to concepts emulation). If there's a complex expression like `is_complete::value && is_trivial::value`, then you'll have to split this up and make sure that `is_trivial` is not instantiated if `is_complete` is false (otherwise UB). For more complex traits, `is_trivial` can be an alias template, so you'd have to delay instantiation. OTOH, `is_trivial(T)` does not immediately instantiate `is_trivial`. – dyp May 12 '15 at 16:33
  • 1
    @Yakk Are you on the library fundamentals group? `std::experimental::is_detected` is your `can_apply`, pretty much. – Barry May 12 '15 at 18:32
  • @Barry nope. Someone else has noticed the similarity and shown me a link to the paper before, however. It was just me getting tired as writing the `void_t` SFINAE template boilerplate again and again. – Yakk - Adam Nevraumont May 12 '15 at 19:44
  • I was wondering: Is it possible to avoid a long and/or complicated expression to get a variable of type `T` and the proper value category inside the SFINAE expression? The best I could come up with is to pass an instantiation of `template struct my_declval{ T&& operator()(); };` instead of `T` to `to_string_t`, and then use `T{}()`, but that's still ugly. – dyp May 12 '15 at 19:48
  • @dyp `templateT&& a();` makes it `a()`, only 1 character longer than yours? Seems rude, as there are only 53 1 character identifiers. – Yakk - Adam Nevraumont May 12 '15 at 19:52
  • Not just that, but it also has ugly template brackets :( I was thinking about variable templates, that is if `expr` is possible (I guess so, using `template extern T&& expr;`) - but I'd much prefer a solution not using any template brackets.. – dyp May 12 '15 at 19:55
  • Sorry I'm taking forever to accept an answer I'm struggling to understand the code in some of these answers. For example I still think I don't understand the point of `void_t` I've asked about it [here](http://stackoverflow.com/q/30302423/2642059), and since you have a good understanding of it perhaps you'd care to weigh in? – Jonathan Mee May 18 '15 at 11:53
  • The point? It takes as input a type, and discards it. I need a `void` there for template specialization pattern matching, but I only want that pattern to match if a particular expression can be evaluated. Sure I could `std::conditional_t< true, void, Z >` instead, but that makes the point where I'm doing the pattern matching more noisy. Once you understand `void_t` as a type function `* -> void`, using conditional which is roughly `bool, t1, t2 -> t1|t2` makes as much sense as preceding every statement in a C++ program with `if (true)`. – Yakk - Adam Nevraumont May 18 '15 at 11:59
  • @Yakk OK I think I have `void_t` under the belt. Next question, can you help me understand what `template – Jonathan Mee May 20 '15 at 10:18
  • @jonathan `Z` is a template argument that is a template itself. I did not need to name it either. I only needed the names in the specialization (below), because I only used the names there. Specializations are pattern matches on arguments passed to primary template, so the primary template needs to take stuff that the specialization will pattern match, even if it does not use it. – Yakk - Adam Nevraumont May 20 '15 at 10:38
  • "I think SFINAE should usually be hidden from interfaces". Disagree. Type requirements should always be a part of the interface. – Paul Fultz II May 28 '15 at 20:01
15

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Is @Yakk on the committee? – Barry May 12 '15 at 17:30
  • @Barry You'll have to ask him :) – T.C. May 12 '15 at 17:38
  • @T.C. Does this mean that I could just define my `ostringstream` overload like: `template enable_if_t<!experimental::is_detected())), T>::value, string> (T t){ return static_cast(ostringstream() << t).str(); }` – Jonathan Mee May 13 '15 at 11:38
  • @JonathanMee No, `is_detected`'s first parameter is a template template parameter. You need an alias template. – T.C. May 13 '15 at 16:00
  • @T.C. Can you help me understand what the alias provides that calling `decltype` inline does not? – Jonathan Mee May 18 '15 at 14:34
  • @JonathanMee Clarity. Without the additional alias, `has_to_string` would look like a mess. No-one wants to mentally parse `template using has_to_string = std::experimental::is_detected())), T>;` – TartanLlama May 18 '15 at 14:47
  • @JonathanMee An alias template is a template and so can be passed as an argument to a template template parameter. – T.C. May 18 '15 at 14:52
  • @T.C. Conversely you're saying that `decltype` cannot be passed as a template argument? Seems like I've done that before... – Jonathan Mee May 18 '15 at 14:54
  • 1
    @JonathanMee It's a template template parameter (hint, that repetition was intentional), and it expects a class or alias template as the argument. – T.C. May 18 '15 at 14:55
  • @T.C. Ugh, that's a bit of a hassle to have to define an alias template for anything I want to pass to `is_detected`, particularly since I already know the type in the calling template. But now that I think about it, if it wasn't a template template parameter it would be evaluated inline, rather than being evaluated by `is_detected`. – Jonathan Mee May 18 '15 at 15:05
  • I have chosen to accept this answer because for future generations this will be the cleanest solution. There are so many good answers here though. As commented in this answer [Yakk's answer](http://stackoverflow.com/a/30195655/2642059) has the capability to achieve what `is_detected` will standardize. I'd also like to point out that for the case where only validating `to_string` will ever be required [Barry's answer](http://stackoverflow.com/a/30190799/2642059) has a simplicity to it that is appealing. – Jonathan Mee May 20 '15 at 11:11
  • @T.C. I've updated my answer to the question that originally prompted this question, I'd appreciate if you could look over it and confirm I'm "doing it right". – Jonathan Mee May 20 '15 at 13:03
  • I wonder if it's worth trying to propose like `template using has_to_string = std::is_valid(std::to_string(std::declval()))`. Where `std::is_valid` is magic. `is_detected` is great, but you still need that extra named type alias somewhere. – Barry Sep 14 '16 at 13:11
  • Sadly `is_detected` still didn't make it into Visual Studio 2019. I added a sample cross platform example of this [here](https://topanswers.xyz/cplusplus?q=1295#a1535). – Jonathan Mee Sep 04 '20 at 00:17
10

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

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • This appears to work well, thanks for the rewrite. It looks like `has_to_string_helper` is a variable template or something, but what is the `->` doing? I'm not familiar with that use of the arrow operator. – Jonathan Mee May 18 '15 at 12:03
  • 1
    `has_to_string_helper` is a template function declaration. We never need to define it as we are just using it as a compile-time construct. The `->` is using C++11's trailing return type syntax. I think it's a bit clearer in this case. – TartanLlama May 18 '15 at 12:06
  • Wouldn't I have to write an overload of `has_to_string_helper` for every type that `to_string` actually accepts for this to correctly return `true_type`? That seems like a lot of work! – Jonathan Mee May 18 '15 at 14:28
  • No, it's templated. The true case will be used if that `decltype` expression is valid, otherwise the false case will be used. – TartanLlama May 18 '15 at 14:30
  • Ah, so the `int` is just cause you're using functions instead of objects to hold your types, so you need an overload. – Jonathan Mee May 18 '15 at 14:38
  • The `int` is there because the `false_type` overload will still be valid, so we need something to disambiguate the call. [Here](http://flamingdangerzone.com/cxx11/overload-ranking/) is a really good article which gives a much nicer way to do this. – TartanLlama May 18 '15 at 14:45
4

I think there are two problems: 1) Find all viable algorithms for a given type. 2) Select the best one.

We can, for example, manually specify an order for a set of overloaded algorithms:

namespace detail
{
    template<typename T, REQUIRES(helper::has_to_string(T))>
    std::string stringify(choice<0>, T&& t)
    {
        using std::to_string;
        return to_string(std::forward<T>(t));
    }
    
    template<std::size_t N>
    std::string stringify(choice<1>, char const(&arr)[N])
    {
        return std::string(arr, N);
    }
    
    template<typename T, REQUIRES(helper::has_output_operator(T))>
    std::string stringify(choice<2>, T&& t)
    {
        std::ostringstream o;
        o << std::forward<T>(t);
        return std::move(o).str();
    }
}

The first function parameter specifies the order between those algorithms ("first choice", "second choice", ..). In order to select an algorithm, we simply dispatch to the best viable match:

template<typename T>
auto stringify(T&& t)
    -> decltype( detail::stringify(choice<0>{}, std::forward<T>(t)) )
{
    return detail::stringify(choice<0>{}, std::forward<T>(t));
}

How is this implemented? We steal a bit from Xeo @ Flaming Dangerzone and Paul @ void_t "can implement concepts"? (using simplified implementations):

constexpr static std::size_t choice_max = 10;
template<std::size_t N> struct choice : choice<N+1>
{
    static_assert(N < choice_max, "");
};
template<> struct choice<choice_max> {};


#include <type_traits>

template<typename T, typename = void> struct models : std::false_type {};
template<typename MF, typename... Args>
struct models<MF(Args...),
                decltype(MF{}.requires_(std::declval<Args>()...),
                         void())>
    : std::true_type {};

#define REQUIRES(...) std::enable_if_t<models<__VA_ARGS__>::value>* = nullptr

The choice classes inherit from worse choices: choice<0> inherits from choice<1>. Therefore, for an argument of type choice<0>, a function parameter of type choice<0> is a better match than choice<1>, which is a better match than choice<2> and so on [over.ics.rank]p4.4

Note that the more specialized tie breaker applies only if neither of two functions is better. Due to the total order of choices, we'll never get into that situation. This prevents calls from being ambiguous, even if multiple algorithms are viable.

We define our type traits:

#include <string>
#include <sstream>
namespace helper
{
    using std::to_string;
    struct has_to_string
    {
        template<typename T>
        auto requires_(T&& t) -> decltype( to_string(std::forward<T>(t)) );
    };
    
    struct has_output_operator
    {
        std::ostream& ostream();
        
        template<typename T>
        auto requires_(T&& t) -> decltype(ostream() << std::forward<T>(t));
    };
}

Macros can be avoided by using an idea from R. Martinho Fernandes:

template<typename T>
using requires = std::enable_if_t<models<T>::value, int>;

// exemplary application:

template<typename T, requires<helper::has_to_string(T)> = 0>
std::string stringify(choice<0>, T&& t)
{
    using std::to_string;
    return to_string(std::forward<T>(t));
}
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
dyp
  • 38,334
  • 13
  • 112
  • 177
  • Shouldn't `(ostringstream{} << x).str()` fail since `ostream` doesn't have an `str()` method? – David G May 12 '15 at 13:08
  • @0x499602D2 Yes, it will fail. It needs to be `static_cast(ostringstream{} << x).str()`: http://stackoverflow.com/q/19665458/2642059 – Jonathan Mee May 12 '15 at 13:13
  • @0x499602D2 Hmmm. I've tested this in fact, and it did work. Not sure why, though. I'll try to find out. – dyp May 12 '15 at 13:18
  • @dyp I think it might be an extension. It certainly isn't supposed to work. – David G May 12 '15 at 13:20
  • 2
    @0x499602D2 Ah yes, it certainly seems so: libc++ defines the following overload: `inline _LIBCPP_INLINE_VISIBILITY typename enable_if<!is_lvalue_reference<_Stream>::value && is_base_of::value, _Stream&&>::type operator<<(_Stream&& __os, const _Tp& __x)` – dyp May 12 '15 at 13:22
  • @0x499602D2 If you're using gcc prior to 5.0 there is a known bug with the move constructor: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316 You could get around this by declaring and inserting into the `ostringstream` then calling `ostringstream::str()` inline. – Jonathan Mee May 12 '15 at 13:44
  • @dyp In Xeo's post, what's with all the `...`s in, e.g., `template>...>` – Barry May 12 '15 at 14:12
  • 1
    @Barry It's for stopping the user from having to provide an extra template parameter. See [this](http://flamingdangerzone.com/cxx11/almost-static-if/). – TartanLlama May 12 '15 at 14:16
  • @dyp I think I understand everything in this post but this line: `struct models()...), void())>` If `MF` is a `has_to_*` that doesn't have a parameterized constructor! So what is `MF(Args...)` doing? – Jonathan Mee May 19 '15 at 14:24
  • @JonathanMee `MF(Args...)` is a function type, like the type of a function `MF my_function(Args...);` Function types can be used to compose types in template arguments. In this case, we compose a metafunction type and the types that shall be used as the metafunction's arguments, without forcing evaluation of the metafunction. We could as well pass a `tuple`. The `models` class template then separates the metafunction from the arguments, and tries to evaluate it (`MF{}.requires_(declval()...)`). – dyp May 19 '15 at 15:13
  • @dyp If `MF` is a function type, what in the world is this: `MF{}.requires_(declval()...)`? There `MF` is behaving like an object. I don't get it. What is `MF`? – Jonathan Mee May 19 '15 at 15:37
  • 1
    @JonathanMee `MF` is not a function type. `MF(int, double)` is a function type. E.g. `struct MF {}; MF my_function(int, double);` then the type of `my_function` is `MF(int, double)` and the type of a pointer to `my_function` is `MF(*)(int double)`. `MF{}` creates an object of type `MF`, and we're calling its member function `requires_` to see if that compiles. In order to pass both `MF` and the argument types as a single template argument, we need to store both in a single type. This can be a `tuple` or a function type like `MF(Args...)`. – dyp May 19 '15 at 15:43
  • With clang, you get better error messages with `std::enable_if` than with `std::enable_if_t`. This is why a macro for `std::enable_if` is preferred. – Paul Fultz II May 28 '15 at 19:55
2

Well, you can just skip all the metaprogramming magic and use the fit::conditional adaptor from the Fit library:

FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional(
    [](auto x) -> decltype(to_string(x))
    {
        return to_string(x);
    },
    [](auto x) -> decltype(static_cast<ostringstream&>(ostringstream() << x).str())
    {
        return static_cast<ostringstream&>(ostringstream() << x).str();
    }
);

Or even more compact, if you don't mind macros:

FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional(
    [](auto x) FIT_RETURNS(to_string(x)),
    [](auto x) FIT_RETURNS(static_cast<ostringstream&>(ostringstream() << x).str())
);

Note, I also constrained the second function as well, so if the type can't be called with to_string nor streamed to ostringstream then the function can't be called. This helps with better error messages and better composability with checking type requirements.

Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
0

My take: to universally determine if something is callable without making verbose type traits for each and every one, or using experimental features, or long code:

template<typename Callable, typename... Args, typename = decltype(declval<Callable>()(declval<Args>()...))>
std::true_type isCallableImpl(Callable, Args...) { return {}; }

std::false_type isCallableImpl(...) { return {}; }

template<typename... Args, typename Callable>
constexpr bool isCallable(Callable callable) {
    return decltype(isCallableImpl(callable, declval<Args>()...)){};
}

Usage:

constexpr auto TO_STRING_TEST = [](auto in) -> decltype(std::to_string(in)) { return {}; };
constexpr bool TO_STRING_WORKS = isCallable<Input>(TO_STRING_TEST);
Sean
  • 393
  • 2
  • 11
0

I find concepts of C++20 easy to read. We can write:

#include<concepts>

template<typename T>
concept has_to_string = requires (T a){ std::to_string(a);};

template<typename T>
auto stringify(T a){
    return "Doesn't have to_string";
}

template<has_to_string T>
auto stringify(T a){
    return "Has to_string";
}

And we can test it like:

int main()
{
    int a;
    int b[2];
    std::cout<<stringify(a); // Has to_string
   std::cout<<stringify(b); // Doesn't have to_string
}

Compiler GCC 10.2 flag -std=c++20.

Sorush
  • 3,275
  • 4
  • 28
  • 42