3

Can someone please explain what is wrong with this pattern? When I try to compile it I get errors for each type of the following type.

error: no match for 'operator=' (operand types are 'std::reference_wrapperstd::__cxx11::basic_string<char>' and 'int') string_reference = property;

struct PropertyPointer {
    int type;
    std::string name;
    union {
        std::reference_wrapper<int*> int_reference;
        std::reference_wrapper<double*> double_reference;
        std::reference_wrapper<float*> float_reference;
        std::reference_wrapper<std::string*> string_reference;
    };
    PropertyPointer(std::string name, auto&& property): name{name} {
        if(std::is_same<decltype(property), int*>::value) {
            type = 'i';
            int_reference = property;
        }
        else if(std::is_same<decltype(property), double*>::value) {
            type = 'd';
            double_reference = property;
        }
        else if(std::is_same<decltype(property), float*>::value) {
            type = 'f';
            float_reference = property;
        }
        else if(std::is_same<decltype(property), std::string*>::value) {
            type = 's';
            string_reference = property;
        }
    }
};
Jeff Greer
  • 113
  • 7
  • 3
    Why don't you use `std::variant`? – eerorika Apr 27 '22 at 08:51
  • 5
    s/if/if constexpr/g - The problem isn't that of either union or reference_wrapper, the problem is that of using templates and not making sure all the code in the template is valid for every type (the if's may not be ever passed through, but the code *is* instantiated). – StoryTeller - Unslander Monica Apr 27 '22 at 08:59

1 Answers1

3

Answer of your question

if statements are not marked constexpr

The reason why your code does not compile is because, even if the if statements will be evaluated to false at compile time, the branches contained by those if statements will be compiled.

In your example, you can mark those if statements as constexpr to compile the code. However, it will (probably) not work as you intend it to, because the type of property will be an rvalue of the pointer you passed to the constructor.

Here is an example of how I made it work (if I understood correctly what you were trying to do:

struct PropertyPointer {
    int type;
    std::string name;
    union {
        std::reference_wrapper<int*> int_reference;
        std::reference_wrapper<double*> double_reference;
        std::reference_wrapper<float*> float_reference;
        std::reference_wrapper<std::string*> string_reference;
    };
    PropertyPointer(std::string name, auto&& property): name{name} {
        using Type = ::std::remove_cvref_t<decltype(property)>;
        if constexpr (std::is_same<Type, int*>::value) {
            type = 'i';
            int_reference = property;
        } else if constexpr (std::is_same<Type, double*>::value) {
            type = 'd';
            double_reference = property;
        } else if constexpr (std::is_same<Type, float*>::value) {
            type = 'f';
            float_reference = property;
        } else if constexpr (std::is_same<Type, std::string*>::value) {
            type = 's';
            string_reference = property;
        }
    }
};

std::variant

It seems to me that you are not familiar with std::variant.

The class template std::variant represents a type-safe union.

source

Its job is to replace unions (that is a C feature) with type safe classes with extra functionalities.

Here is a quick example of how I would have done it:

class PropertyPointer {
public:
    PropertyPointer(
        auto&& property
    )
        : m_value{ &property }
    {
        // compare types if you want it to compile but have the object empty
    }

    template <
        typename T
    > [[ nodiscard ]] bool hasType() // true if the types matches the one inside (pointer or not)
    {
        using Type = ::std::remove_cvref_t<::std::remove_pointer_t<::std::remove_cvref_t<T>>>;
        return std::holds_alternative<Type*>(m_value);
    }

private:
    ::std::variant<int*, float*, double*, ::std::string*> m_value;
};

int main()
{
    PropertyPointer property{ ::std::string{ "yes" } };
    property.hasType<int>(); // false
    property.hasType<float>(); // false
    property.hasType<double>(); // false
    property.hasType<::std::string>(); // true
    property.hasType<const ::std::string>(); // true
    property.hasType<::std::string&>(); // true
    property.hasType<const ::std::string*>(); // true
    property.hasType<const ::std::string *const>(); // true
}

This example is here to show you that you can add some extra features like comparing with and without the pointer for the same behavior.

This example isn't here to tell you that "it is how it has to be done" but rather an example of how modern c++ features can make your life easier.

DiantArts
  • 104
  • 7
  • 1
    Thank you very much for the answer and the examples of more modern ways. This is actually what I was hopping for as I learn Modern C++. – Jeff Greer Apr 28 '22 at 02:31
  • @JeffGreer No problem at all, Im glad it helped. – DiantArts Apr 28 '22 at 02:33
  • @JeffGreer A quick note: I realised after posting it that you store a pointer to an rvalue (object about to be destroyed). I dont know why you want to do that,but that is probably something you'll have to change (to an lvalue reference for example) – DiantArts Apr 28 '22 at 02:34
  • Why do you use class versus struct in this circumstance? – Jeff Greer Apr 28 '22 at 02:46
  • Yes, I am wanting to update a static value that is place in a configuration object. I am doing getter/setter pattern to be able to view the value as well as update it. – Jeff Greer Apr 28 '22 at 02:53
  • @JeffGreer because in C++ (by convention), structs are used for datas, while classes are more used for objects containing some form of logic. From what I know, people tend to say that if you need more than just constructors, destructors and operators, you should create a class. If you wanna read about it, I recommand [this post](https://stackoverflow.com/questions/54585/when-should-you-use-a-class-vs-a-struct-in-c), or to search something else more clear for you. Either way, I recommand searching for good practice guides/comments to get you started and understand the logics behind it – DiantArts Apr 28 '22 at 02:54
  • BTW: I am passing in a lvalue and forwarding it to this class. So PropertyPointer receives the the unadulterated memory address. – Jeff Greer Apr 28 '22 at 05:48