choeger
choeger

Reputation: 3567

Polymorphic construction of a std::variant

I want to implement a simple error return type, i.e. a variant where one case indicates an error and the other indicates the succesfully computed result. For that task, I want to create error constructors like so:

#include <variant>
#include <string>
#include <iostream>

template <typename A> using result_t = std::variant<int, A> ;

template <typename T> result_t<T> make_left_int()
{
    return 42;
}

int main ()
{
    result_t<double> a = make_left_int();
    result_t<void*> b = make_left_int();

    std::cout << std::get<int>(a) << std::endl << std::get<int>(b) << std::endl;
}

The constrcutor make_left_int is polymorphic regardin the type of the success variant. But apparently, the C++ compiler cannot type it. It fails to infer the (unused) template argument T. Or rather it fails to unify it with double and void*.

Is there a way to have a function like that?

Upvotes: 1

Views: 469

Answers (2)

Useless
Useless

Reputation: 67772

The constructor make_left_int ...

But make_left_int is not a constructor in C++. Unlike the functional language(s) whose terminology you're presumably using, a C++ constructor is a special method of a class, used to initialize instances of that class. It cannot be a free function which just returns an instance of that class.

... polymorphic regarding the type of the success variant

Yes, but: C++ template type deduction only operates on arguments to a function. For example,

template <typename T> T return_default() { return T{}; }

won't work in this code, because the return type is not available for deducing the type parameter T:

int i = return_default();
double d = return_default();

although you can still pass the type parameter explicitly:

int i = return_default<int>();

The simplest solution is the one _nh proposed in a comment: deduce the return type from the call, rather than the other way around (this avoids multiplying the places you need to specify the result type)

auto a = make_left_int<double>();

I suspect it would also be better style to specify what the member means, rather than where it sits in the record, like

auto a = error_result<double>(42);

or you could use an actual constructor, for your own variant-style class, ideally with a tag type to distinguish error codes from successes, in case the successful result could also be an int:

result<double> a(Error{42});

NB. polymorphic in C++ is often assumed to mean runtime polymorphism by default, as opposed to the static/compile-time polymorphism discussed here. If you're using the template keyword, you can always just say template and be understood.

Upvotes: 2

nh_
nh_

Reputation: 2241

You can just let your make_left_int() return int, since any result_t<T> is constructible from an int. Note, however, that you might get into trouble with result_t<int>.

I recommend writing a result type yourself, not relying on variant. This has the benefit, that you can handle such cases (having a result<int>) and it allows you to handle access to the value when actually an error is stored, in your own way.

Upvotes: 0

Related Questions