2
votes

While attempting to add support for UTF-8 locales in a library, I added the type std::wstring to the boost::variant that holds a value.

At that point, I start to get errors with something down inside boost::variant:

Blockquote/opt/TWWfsw/libboost147/include/boost/variant/detail/variant_io.hpp: In member function 'void boost::detail::variant::printer::operator()(const T&) const [with T = std::basic_string, std::allocator >, OStream = std::basic_ostream >]':
/opt/TWWfsw/libboost147/include/boost/variant/variant.hpp:858: instantiated from 'typename Visitor::result_type boost::detail::variant::invoke_visitor::internal_visit(T&, int) [with T = const std::basic_string, std::allocator >, Visitor = boost::detail::variant::printer > >]'
< SNIP SNIP >
Cursor.H:84: instantiated from here /opt/TWWfsw/libboost147/include/boost/variant/detail/variant_io.hpp:64: error: no match for 'operator<<' in '((const boost::detail::variant::printer > >)this)->boost::detail::variant::printer > >::out_ << operand'
/opt/TWWfsw/gcc44/include/c++/ostream:108: note: candidates are: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>& (
)(std::basic_ostream<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits]
etc.

This example is using boost-1.47 w/ g++ 4.4.

#include <string>
#include <iostream>
#include <boost/variant.hpp>
#define NO_STRING 1

int main (int argc, char** argv)
{
#if defined(NO_STRING)
  boost::variant<int, std::wstring> v;
#else
  boost::variant<int, std::wstring, std::string> v;
#endif
  v = 3;
  std::wcout << v << std::endl;
  std::wstring T(L"wide char literal");
  v = T;
  std::wcout << v << std::endl;
  return 0;
}

This program will output:

3
wide char literal

but if the #define is removed, and both string and wstring are in the variant template parameters, the same error results.

My question is, can I create something that will satisfy this missing definition, like a template specialization?

Perhaps defining a variant visitor that converts the wide string to a narrow string? (not a general solution, but narrowing would work in my case)

The problem comes from using << with the variant when both strings are defined. Even if I only output the int through the variant, it won't compile.

2

2 Answers

3
votes

You problem is not boost::variant can't handle both string and wstring. The problem is that wcout cannot handle std::string. For more info about this, see Why is it that wcout << ""; is OK but wcout << string(); is not?

1
votes

Building on the answer by @John Bandela: He is actually reight. I'll just explain a bit more about the culprit.

Notice that std::cout << std::string() and std::wcout << std::wstring() are valid while std::wcout << std::string() and std::wcout << std::string() are not. This is simply due to the definition of the stream operator (see Why is it that wcout << ""; is OK but wcout << string(); is not?) It is simply not possible unless you provide an overload that handles this.

The same applies to variants: If you try to stream a variant<std::string> into std::wcout or a variant<std::wstring> into std::cout you will get the same error.

The bottom of your problem is: The operation you apply to the variant (visit, stream-operator) has to support all possibly contained types. That is due to the implementation which is like (pseudo-code):

void variant::visit(T operation){
  if(contained is T1) operation(static_cast<T1>(contained));
  else if(contained is T2) operation(static_cast<T2>(contained));
  ...
}

So a solution to your problem would be: Implement the operation yourself for all types:

#include <string>
#include <iostream>
#include <boost/variant.hpp>

std::wstring stringToWString(const std::string& v){
    // Only works for single-byte strings. Do it better!
    std::wstring result(v.begin(), v.end());
    return result;
}

struct visitor: boost::static_visitor<void>{
    template<typename T>
    void operator()(const T& v) const { std::wcout << v; }
    void operator()(const std::string& v) const { std::wcout << stringToWString(v); }
};

int main (int argc, char** argv)
{
  boost::variant<int, std::wstring, std::string> v;
  v = 3;
  boost::apply_visitor(visitor(), v);
  std::wcout << std::endl;
  std::wstring T(L"wide char literal");
  v = T;
  boost::apply_visitor(visitor(), v);
  std::wcout << std::endl;
  v = std::string("Narrow string");
  boost::apply_visitor(visitor(), v);
  std::wcout << std::endl;
  return 0;
}

http://coliru.stacked-crooked.com/a/35e156f38208af1e

Final Note: Do not support UTF-8 by using std::wstring! UTF-8 can be stored in a regular std::string. There are very(!) few places where you want to convert this to anything else. One of them is dealing with WinAPI. For that you can write wrappers, that accept a std::string and convert right before passing it to the actual API.

So general advice: Use UTF-8 (in std::string) everywhere in your program and only convert at border points.