Как преобразовать что-либо в строку неявно?

Моя цель - разработать класс String, который декорирует std::string для того, чтобы обеспечить некоторую функциональность, необходимую моей программе. Одна функциональность, которую я хочу добавить, - это возможность неявно преобразовывать что-либо в мою строку, чтобы сэкономить время при наборе текста.

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

std::ostream& operator<<(std::ostream& o, const String& s);

class String {
public:
    template<typename t_value>
    String::String(t_value value) {
       std::ostringstream oss;
       oss << value;
      _str = oss.str();
    }
private:
    std::string _str;
}

Это прекрасно работает с любым типом, который имеет <<оператор определен. Проблема возникла с любым классом, у которого нет оператора потока. Ошибка компилятора была бы в порядке, но я получил рекурсию бесконечности, так как C++ пытается использовать мой глобальный << оператор, чтобы попытаться преобразовать в мой тип String.

Моя основная задача - написать код

class Foo {
    int _memberWithUnderscoreInName;
}

String s = Foo();

И получить ошибку компилятора вместо бесконечного цикла в конструкторе.

Есть ли простое решение для этого?

2 ответа

Решение

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

class String {
public:
    // This must be implemented inline here
    friend std::ostream& operator<<(std::ostream& o, const String& s) {
        return o << _str; // for example
    }

    template<typename t_value>
    String(t_value value) {
       std::ostringstream oss;
       oss << value;
      _str = oss.str();
    }
private:
    std::string _str;
};

Теперь он может быть найден только путем поиска, зависящего от аргумента, и поэтому будет рассматриваться только в том случае, если второй аргумент действительно имеет тип String, а не просто конвертируется в него. Таким образом, он не будет рассматриваться в качестве кандидата на os << value в конструкторе, давая ошибку компиляции, а не смертельную спираль во время выполнения, если нет другого кандидата.

Это или подобное должно исправить это:

namespace HasFormattedOutput {

    namespace Detail
    {
        struct Failure{};
    }

    template<typename OutputStream, typename T>
    Detail::Failure operator << (OutputStream&, const T&);

    template<typename OutputStream, typename T>
    struct Result : std::integral_constant<
        bool,
        ! std::is_same<
            decltype((*(OutputStream*)0) << std::declval<T>()),
            Detail::Failure
        >::value
    > {};
} // namespace HasFormattedOutput

template <typename T, typename OutputStream = std::ostream>
struct has_formatted_output
:   HasFormattedOutput::Result<OutputStream, T>
{};

class X  {
    public:
    X() {}

    template <typename T>
    X(const T& t) {
        static_assert(
             has_formatted_output<T>::value, 
             "Not supported type.");
        std::ostringstream s;
        s << t;
        str = s.str();
    }

    private:
    std::string str;
};
std::ostream& operator << (std::ostream& stream, const X&) { return stream; }

struct Y  {
    Y() {}
};

int main() {
    Y y;
    X x(y);
    return 0;
}
Другие вопросы по тегам