Implicit conversion from a struct

151 Views Asked by At

This is my struct which uses implicit casting on creating variable.

#include <string>
#include <variant>

using namespace std;
using val = variant<int, string, double, bool, long, long long, long double>;

struct value
{
    val innerVal;
    value():innerVal(""){}
    value(const val &c) : innerVal(c) {}

    template <typename T>
    operator T()
    {
          return get<T>(innerVal);
    }

    template <typename V>
    value &operator=(const V &t)
    {
        innerVal = t;
        return *this;
    }
};

This is how I am using it when I construct a variable it works fine but when I assigned an already created variable to value struct it gives error.

int main(int argc, char* argv[])
{
    value h;
    h = "String";
    string m = h;// Here works fine
     string b = "different";
     b = h;//Here it gives error 
}

Compiler error



use of overloaded operator '=' is 
ambiguous (with operand types 
'std::__ndk1::string' (aka 
'basic_string<char, char_traits<char>, 
allocator<char> >') and 'value')

        b = h;

I want to implicitly construct the variable without encountering this error can someone explain how to resolve this issue.

2

There are 2 best solutions below

1
0xbachmann On BEST ANSWER

The assignment b = h is ambiguous, because std::string has different operator=. Because you have a templated conversion operator your compile does not know which overloaded operator= of std::string to take.

You can constrain the types of conversions allowed from value to allow only implicit conversions to one of the member variants type. The following struct will provide a boolean telling us, whether type T is contained in Type's template parameters, using a fold expression:

template<typename, typename>
struct has_template_param;

template<typename T, template<typename ...> typename Type, typename ...Ts>
struct has_template_param<T, Type<Ts...>> {
    static constexpr bool value = (... || std::is_same_v<T, Ts>);
};

Then the templated conversion operator can be constrained with SFINAE:

template<typename T, typename = std::enable_if_t<has_template_param<T, val>::value>>
operator T() {
    return std::get<T>(innerVal);
}

Using C++20 concepts, it can be done more conveniently via

template<typename T, typename Type>
concept has_param = has_template_param<T, Type>::value;

and

template<has_param<val> T>
operator T() {
    return std::get<T>(innerVal);
}

See demo

0
Swift - Friday Pie On

std::variant desinged the way it is for reason, and one of the reasons is te problem you stumbled upon. Stored types may have overloaded copy and initialization oeprations.

This version of value is would work, although it still have some flaws and limitations of std::variant:

#include <string>
#include <variant>

using val = std::variant<int, std::string, double, bool, long, long long, long double>;

struct value
{
    val innerVal;

    value():innerVal(""){}

    // To make value copyable and movable
    value(const value&) = default;
    value(value&&) = default;

    // this would handle assignments.
    value &operator=(const value&)  = default;
    value &operator=(value&&)  = default;

    // to allow initialization in assignment form we must mimic std::variant, 
    // value(const val&) would not be enough
    template <typename V> value(V&& v):innerVal(v){}

    template <typename T>
    operator T()
    {
          return get<T>(innerVal);
    }

    // we don't need generic a operator= for every type, but we can have
    // overloads
};

int main(int argc, char* argv[])
{
    value v1 = 3;
    value v2 = "String";
    value v3;
    
    v3 = "Another String";
    // std::string s1 = v1; // here you'll get exception, do you want rectify that?
    std::string s2 = v2;    // Here works fine
    s2 = std::string(v2);   // std::string is at fault here
    v1 = 42;
    v2 = v3;                // now works fine
    
}