3

Have a simple wrapper struct

template<typename T>
struct named_value {
    using type = T;
    T value{};
    string_view name = "unnamed_value";

    constexpr explicit operator T&(){ return value; }
    constexpr explicit operator T const&()const{ return value; }
    constexpr explicit operator T &&()&&{ return std::move(value); }

    template<typename U=T>
    constexpr named_value& operator=(U&&v){ value = std::forward<U>(v); return *this; }

    friend auto operator<<(std::ostream& os, named_value const& a) ->ostream&
    {
        return os<<a.name<<":"<<a.value;
    }
};

Here I try to use universal reference for operator= in order to write less overloads (i.e. for ref, rval ref, const ref). Is this right way?

kyb
  • 7,233
  • 5
  • 52
  • 105

1 Answers1

4

It's a problematic implementation. The problem is that it may interfere with regular assignment. The compiler generates operator=(name_value const&v) for you, and it takes by a const reference. But when the source object is non-const, the overload generated from the template specialization becomes a better match.

For instance, this code

named_value<int> a;
a = a;

produces this error with GCC

main.cpp: In instantiation of 'constexpr named_value<T>& named_value<T>::operator=(U&&) [with U = named_value<int>&; T = int]':
main.cpp:18:10:   required from here
main.cpp:13:51: error: cannot convert 'named_value<int>' to 'int' in assignment
   13 |     constexpr named_value& operator=(U&&v){ value = std::forward<U>(v); return *this; }

You need to counter it with SFINAE. So, if we say that things that may go to the compiler generated overload should be rejected. Then ...

template<typename U=T, std::enable_if_t< !std::is_convertible_v<U&&, named_value> , int> = 0>
constexpr named_value& operator=(U&&v){ value = std::forward<U>(v); return *this; }

or, if doing C++20

template<typename U=T> requires (!std::convertible_to<U&&, named_value>)
constexpr named_value& operator=(U&&v){ value = std::forward<U>(v); return *this; }

You may of course opt for other checks, such as std::is_same or std::same_as, depending on the exact semantics you wish to achieve.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458