Is there any practical reason why std::get_if (std::variant) takes a variant argument by pointer instead of by value/&/const&?
Asked Answered
T

3

14

I've never used std::get_if, and since its name is different from std::get, I don't see a reason why its argument should be a pointer¹ (whereas std::get has a by-reference parameter).


¹If it was named std::get too, then overload resolution would be a reason enough.


Yes, my question could be duped to the question Is it absolutely necessary for std::any_cast() and std::get_if(std::variant) to take pointer as an argument?, but the point is that there's no answer there that addresses std::get_if vs std::get, just one comment; the only answer concentrates on std::any_cast.

Tracee answered 21/9, 2021 at 14:41 Comment(3)
I seem to recall Jason Turner talking about why that is in one of the C++ Weekly videos.Pepita
Does this answer your question? Is it absolutely necessary for std::any_cast() and std::get_if(std::variant) to take pointer as an argument?Estonian
@xskxzr, no, for the reason I've added in the question.Tracee
L
19

This is because get_if is noexcept, so an exception will never be thrown. In order to achieve this, it must return a pointer so that nullptr can be returned when the access fails.

Because it returned the pointer, it must take the pointer of the variant. If it takes the reference of variant, then it must be able to accept the type of variant&, const variant&, variant&& and const variant&&, but it does not make sense for the pointer to remain ref-qualified.

Considering that get_if accepts variant&&, what you do is return the address of an xvalue, which is terrible. Even if get_if only allows variant& and const variant&, the latter can still accept a variant&& and return a dangling.

Liard answered 22/9, 2021 at 4:28 Comment(8)
I truly don't understand the explanation in the second paragraph.Tracee
@Enlico. Assuming get_if accepts references, what type should it return when it accepts variant&&?Enunciation
variant&& would bind only to rvalues, right? Then wouldn't returning a nullptr be the right choice?Tracee
@Enlico. That can be a choice, but it also means that get_if(std::move(v)) will return a nullptr even if there is no problem returning its address.Enunciation
It's just like unique_ptr.get() I don't see the reason it's any more invalid for variant.Dial
The variant && issue can be easily resolved with an = delete overload, if necessary.Joeyjoffre
This seems to be describing why get_if returns a pointer, not why it takes a pointer.Warr
@DavidStone it could, but taking a pointer means that the result is at the same level of pointerness, which matches how all the _casts work.Unfaithful
T
6

From what I can tell, it is based off dynamic cast logic. The dynamic cast that can fail takes a pointer and returns a pointer.

Similarly, get that can fail takes a pointer and returns one.

But really it looks like a tiny bikeshed decision of not much importance.

If we had these 3 overloads:

T const* std::get_if<T>( variant const& );
T* std::get_if<T>( variant & );
T* std::get_if<T>( variant&& ) = delete;

it would work fine. The decision to take an argument by pointer is not based off of the problem with taking references.

With dynamic_cast and any_cast etc, the convention is the signature is:

T& XXX_cast<T>(X&);
T* XXX_cast<T>(X*) noexcept;

ie, a throwing "cannot fail" reference cast, and a non-throwing "can fail" pointer cast. get_if following that pattern is probably the primary reason why it takes a pointer.

Now, with the pointer version, you can do this:

using A = std::variant< int, double >;
using B = std::variant< std::string, char >;
std::variant< A, B > bob;
int* pint = std::get_if<int>( std::get_if<A>( &bob ) );

which is much more complex without get_if taking a pointer.

A better question is probably why it is called std::get_if and not std::get; or, rather why it isn't called std::dynamic_cast_if.

Terryl answered 21/9, 2021 at 14:45 Comment(0)
C
4

This design allows you to apply "chained" invocations like this:

struct A : public std::variant<int, float>{};

int main()
{
    std::variant<A, char> f;
    A a;
    static_cast<std::variant<int, float>&>(a) = 10;
    f = a;
    cout << *std::get_if<int>(std::get_if<A>(&f));
    return 0;
}
Clawson answered 9/3, 2023 at 13:24 Comment(1)
It took me a bit to spot the double get_if here; only after I edited a similar answer elsewhere did I realize you did it here! Highlighting it with a follow up would helpTerryl

© 2022 - 2024 — McMap. All rights reserved.