Ambiguous overload for string::operator=

Hi

I'm trying to build a small csv parsing utility which implicitly casts a csv token to the data type I'm trying to assign it to.

However, while working for fundamental types, it fails for std::string, because std::string has multiple versions of operator=. The compiler doesn't resolve against operator std::string() in token_cast_proxy for std::string types, but rather the operator T.

How can I force the operator std::string() to be chosen for assignment?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <sstream>
#include <string>
#include <iostream>
#include <boost/lexical_cast.hpp>

namespace csv {

struct token_cast_proxy
{
    std::string &tok;
    token_cast_proxy(std::string &i) : tok(i) { }

    operator std::string() const { return tok; }

    template<typename T>
    operator T() const { return boost::lexical_cast<T>(tok); }

};

inline token_cast_proxy token(std::istream &in)
{
    std::string tok;
    std::getline(in, tok, ',');
    return token_cast_proxy(tok);
}

} // csv

int main()
{
    std::string line = "foo,1";
    std::istringstream p(line);

    std::string foo;
    int val;
    foo = csv::token(p);    // this doesn't work
    val = csv::token(p);    // this works

    std::cout << "foo=" << foo << ", val=" << val << std::endl;
    
    return 0;
}
Change

 
operator std::string() const;


to a specialization of the templated operator T.

1
2
template<>
operator std::string<std::string>() const;


(Not sure on above syntax, but the general idea is to use template specialization.)
So I did what you suggested:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct token_cast_proxy
{
    std::string &tok;
    token_cast_proxy(std::string &i) : tok(i) { }

    template<typename T>
    inline operator T();
};

template<typename T>
inline token_cast_proxy::operator T()
{ return boost::lexical_cast<T>(tok); }

template<>
inline token_cast_proxy::operator std::string()
{ return tok; }


Unfortunately that didn't resolve the problem. The compiler can't choose which operator=() to use, and it's looks like because the operator T() is being chosen over the operator std::string()?!?

(If I remove the operator T() and replace it with just operator std::string() it compiles fine (for the string = csv::token() )

src/main.cpp:53: error: ambiguous overload for ‘operator=’ in ‘foo = csv::token(((csv::parse&)(& p)))’
/usr/include/c++/4.4/bits/basic_string.h:505: note: candidates are: std::basic_string<_CharT, _Traits, _Alloc>& std::basic_string<_CharT, _Traits, _Alloc>::operator=(const std::basic_string<_CharT, _Traits, _Alloc>&) [with _CharT = char, _Traits = std::char_traits<char>, _Alloc = std::allocator<char>]
/usr/include/c++/4.4/bits/basic_string.h:513: note:                 std::basic_string<_CharT, _Traits, _Alloc>& std::basic_string<_CharT, _Traits, _Alloc>::operator=(const _CharT*) [with _CharT = char, _Traits = std::char_traits<char>, _Alloc = std::allocator<char>]
/usr/include/c++/4.4/bits/basic_string.h:524: note:                 std::basic_string<_CharT, _Traits, _Alloc>& std::basic_string<_CharT, _Traits, _Alloc>::operator=(_CharT) [with _CharT = char, _Traits = std::char_traits<char>, _Alloc = std::allocator<char>]
/usr/include/c++/4.4/bits/basic_string.h:536: note:                 std::basic_string<_CharT, _Traits, _Alloc>& std::basic_string<_CharT, _Traits, _Alloc>::operator=(std::initializer_list<_CharT>) [with _CharT = char, _Traits = std::char_traits<char>, _Alloc = std::allocator<char>]


Last edited on
Consider specifying T explicitly when you get your token.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <sstream>
#include <string>
#include <iostream>
#include <sstream>

//I'm not a fan of boost,
//so I cooked up my own lexical_cast.

template <class T>
struct lexical_cast
{
    std::string data;

    lexical_cast(const std::string & str)
    {
        data=str;
    }

    operator T()
    {
        T ret;
        std::istringstream buffer(data);

        buffer>>ret;

        return ret;
    }
};

namespace csv {

struct token_cast_proxy
{
    //This shouldn't be a reference,
    //it would cause problems in your token function.

    std::string tok;

    token_cast_proxy(std::string &i) : tok(i) {}

    template <class T>
    T as() const;
};

template <class T>
T token_cast_proxy::as() const
{
    std::cout << "template" << std::endl;
    return lexical_cast<T>(tok);
}

template <>
std::string token_cast_proxy::as<std::string>() const
{
    std::cout << "specialization" << std::endl;
    return tok;
}

inline token_cast_proxy token(std::istream &in)
{
    std::string tok;
    std::getline(in, tok, ',');
    return token_cast_proxy(tok);
}

} // csv

int main()
{
    std::string line = "bar,7,-2.66";
    std::istringstream p(line);

    std::string foo;
    int vali;
    double vald;

    foo = csv::token(p).as<std::string>();
    vali= csv::token(p).as<int>();
    vald= csv::token(p).as<double>();

    std::cout << "\nfoo=" << foo << ", vali=" << vali
        << ", vald=" << vald << std::endl;

    return 0;
}

Mmmm, your problem isn't ambiguity between which operator() to call.. it's ambiguity
as to which std::string::operator=() to call.

You have a design flaw here; I'm not sure there is a simple answer to your question.

Your templated operator() basically says that you can implicitly convert a token_cast_proxy
to absolutely any type whatsoever.

The compiler has to deduce T from how you are using the return type, which in this case
is storing the result in a std::string through operator=. But std::string has multiple
operator=, each of which obviously takes a different argument. So how's the compiler
to decide which one to call? Should it call operator= that takes a std::string? Or
the operator= that takes a const char? Or what? There is nothing in your code that
tells the compiler how to do the assignment.

I would seriously reconsider the "implicit cast to anything" design.
The implicit cast to anything works in that if an implicit cast is not available, you fail at compile time. I understand that there are inherent dangers involved, such as an implicit cast you're not expecting, leading to data corruption and difficult to diagnose runtime errors.

However, for something as inane as a pod csv parser, (ahem, yes, I guess I shouldn't call std::string pod, but...) it's something where I'll take the bad with the good.

I have a somewhat sneaky solution, but it's not ideal, and only resolves the ambiguity for std::string's overloaded operator=. The same ambiguity will arise for any type with overloaded operator=. Since I intend to only use this for pod structures that shouldn't be a problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace csv {
    
struct token_cast_proxy : std::string
{
    template<typename T>
    operator T() { return boost::lexical_cast<T>(*this); }
};

inline token_cast_proxy token(std::istream &in)
{
    token_cast_proxy tok;
    std::getline(in, tok, ',');
    return tok;
}

} // namespace csv 

Topic archived. No new replies allowed.