0

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.

11
  • 1
    "which uses implicit casting..." There's no such thing as "implicit casting". The correct term is "implicit conversion". Commented Feb 28, 2024 at 11:43
  • 1
    value h = "String"; won't compile. See demo Commented Feb 28, 2024 at 11:44
  • Trust me implicit conversions can really bite in (big) code bases, so stay away from them. What actual problem do you want to solve? If it is just to avoid some explicit type casting (numbers characters you have to type)... don't do it and take the little pain of a little effort right now. Commented Feb 28, 2024 at 11:45
  • The difference is that, in string m = h; there is no assignment, just a call to the std::string constructor (the = is an initialisation, here, rather than an assignment). In the second case, you are actually using an assignment. Commented Feb 28, 2024 at 11:46
  • Also, what compiler are you using? Mine (MSVC, with Standard set to C++17) doesn't like value h = "String"; but will accept value h{ "String" }; Commented Feb 28, 2024 at 11:47

2 Answers 2

5

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

Sign up to request clarification or add additional context in comments.

1 Comment

There are case where concept approach wouldn't work (i.e. target type may accepting two different matching types) but for string it works.
3

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
    
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.