Резервный вариант SFINAE, когда функция не существует

В настоящее время я пытаюсь реализовать функцию toString, которая вызывает .toString() или std::to_string() в зависимости от того, что доступно для выведенного типа.

Пока у меня есть этот рабочий фрагмент:

#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*");
}

Теперь я хочу вставить static_assert, когда никакая перегрузка из приведенных выше не является жизнеспособной, потому что сообщение об ошибке GCC по умолчанию для более старых версий GCC не очень информативно.

Как я могу проверить, не возможны ли ни .toString(), ни std::to_string() для T?

До сих пор я не нашел способа проверить, отсутствует что-то, только наоборот. Я надеюсь, что у кого-то есть идея, как решить эту проблему, и спасибо за ваше время.


person Alexander Weinrauch    schedule 08.10.2017    source источник
comment
decltype(std::to_string(obj), std::string()) - ТИЛ. Но вы должны void первое выражение. Перегруженный оператор запятой от непослушного пользователя может испортить ваш SFINAE   -  person StoryTeller - Unslander Monica    schedule 08.10.2017
comment
@max66 - Нет смысла повторяться. И версия OP будет работать со всем, что можно преобразовать в std::string, при этом фиксируя возвращаемый тип функции. Довольно умно.   -  person StoryTeller - Unslander Monica    schedule 08.10.2017
comment
@StoryTeller - ты прав.   -  person max66    schedule 08.10.2017


Ответы (3)


Вы также можете использовать static_assert с собственным сообщением об ошибке:

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 "";
}

живой пример

person m.s.    schedule 08.10.2017
comment
Спасибо, именно то, что я хотел. Но почему этот не выбран среди других вариантов? - person Alexander Weinrauch; 08.10.2017
comment
@В.Ф. каким путем? - person m.s.; 08.10.2017
comment
@В.Ф. означает ли действительная специализация, что static_assert не запускается? - person m.s.; 08.10.2017
comment
ср. stackoverflow.com/questions/40076078/ - person W.F.; 08.10.2017

Вам нужно ввести перегрузку, которая хуже всех других, которые у вас есть на данный момент, и удалить ее. Вам не нужно проверять, не существует ли ни одна из строковых функций таким образом.

Популярный способ сделать это — использовать вариативные аргументы в стиле C:

std::string toString(...) = delete;
person Rakete1111    schedule 08.10.2017
comment
Спасибо, пока вы можете удалить функцию, не являющуюся членом :). Это самый простой ответ, но я предпочитаю ответ @m.s из-за пользовательского сообщения об ошибке, которое в этом случае более выразительно. - person Alexander Weinrauch; 08.10.2017

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>;

Я оставляю can_std_to_string в качестве упражнения.

Если в стандартной версии вам не хватает void_t:

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

Что работает даже в ранних c++ 11 компиляторы.

person Yakk - Adam Nevraumont    schedule 08.10.2017