Неоднозначная перегрузка С++ для универсального оператора шаблона ostream ‹‹

Этот вопрос следует за моим предыдущим вопросом: Generic operator‹‹ ostream C++ для строкового класса где я хотел бы реализовать общий оператор <<ostream, который будет работать для любого класса, которому принадлежит метод to_str().

Мне удалось проверить, реализует ли класс метод to_str() и использовать ли std::cout << stringify(a) благодаря этому ответу. Однако у меня возникли трудности с написанием шаблонных операторов ostream<<, чтобы заставить работать std::cout << a.

Следующий тестовый код:

#include <iostream>
#include <sstream>
#include <string>

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

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

template<typename T>
struct has_to_string<T, 
    void_t<decltype(std::declval<T>().to_str())>
    >
: std::true_type { };

template<typename T> std::enable_if_t<has_to_string<T>::value, std::string> 
stringify(T t) { 
    return t.to_str(); 
} 

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

// The following does not work
/*
template<typename T> std::enable_if_t<has_to_string<T>::value, std::ostream&> 
operator<<(std::ostream& os, const T& t) {
    os << t.to_str();
    return os;
}

template<typename T> std::enable_if_t<!has_to_string<T>::value, std::ostream&> 
operator<<(std::ostream& os, const T& t) {
    os << t;
    return os;
}
*/

struct A {
    int a;
    std::string to_str() const { return std::to_string(a); }
};

struct B {
    std::string b;
    std::string to_str() const { return b; }
};

int main() {
    A a{3};
    B b{"hello"};
    std::cout << stringify(a) << stringify(b) << std::endl;    // This works but I don't want to use stringify
    // std::cout << a << b << std::endl;               // I want this but it does not work
}

выдает ту же ошибку, что и в исходном вопросе. Что я делаю не так ?


person coincoin    schedule 29.05.2015    source источник
comment
Версия с !has_to_string<T>::value производит бесконечную рекурсию, поскольку os << t вызывает себя.   -  person Jarod42    schedule 29.05.2015
comment
Спасибо за вашу точку зрения!   -  person coincoin    schedule 30.05.2015


Ответы (1)


Вы получаете сообщение об ошибке неоднозначная перегрузка для 'operator‹‹, когда используется тип std::string, потому что шаблонная версия в вашем коде имеет такой же приоритет, как и версия, отправленная в заголовке ostream.

Вы можете проверить, что это является источником вашей проблемы, изменив свою тестовую программу следующим образом:

int main() {
    std::cout << std::string("There is your problem") << std::endl;
}

И вы все равно увидите ту же ошибку.

Чтобы решить эту проблему, вы можете добавить явное определение оператора‹‹, которое будет иметь приоритет над двумя конфликтующими шаблонами.

std::ostream& operator<<(std::ostream& os, const std::string& t) {
    using std::operator<<;
    os << t;
    return os;
}
person Nielk    schedule 29.05.2015
comment
О, спасибо. Действительно, это сработало ... Почему возникает ошибка, когда тип std::string now ? И если возможно, можно ли сделать более лаконичный код вместо того, чтобы писать 3 overloaded operator<< ? - person coincoin; 29.05.2015
comment
@coincoin Отредактируйте мой ответ, чтобы уточнить это - person Nielk; 29.05.2015
comment
@coincoin Я не знаю никакой техники, чтобы избежать перегрузки другого оператора. Строго говоря, вы даже должны подумать о написании перегрузок для других типов строк (std::wstring и т. д.). - person Nielk; 29.05.2015
comment
Спасибо. Я начинаю верить, что этот поиск универсальности как-то опасен, так как я меняю стандартное поведение... - person coincoin; 30.05.2015