2

I'm trying to implement something like Rust's Result<T,E> type in C++, which is a union that holds either T or E value.

Some of its constructors are:

template <typename T, typename E>
Result<T,E>::Result(const T& value) : isOk(true), value(value) {}

template <typename T, typename E>
Result<T,E>::Result(T&& value) :  isOk(true), value(std::move(value)) {}

It works as I expect for T and E being non-reference types or pointers, but fails to compile if any of the underlying types is a reference. For example:

MyType my_object;
Result<MyType&, AnyOtherType> result(my_object);

produces the following error:

./result.h:46:5: error: multiple overloads of 'Result' instantiate to the same signature 'void (MyType &)'
    Result(T&& value);
    ^
main.cpp:39:23: note: in instantiation of template class 'Result<MyType &, int>' requested here
  Result<MyType&,int> result(object);
                      ^
./result.h:37:5: note: previous declaration is here
    Result(const T& value);
    ^

I understand that this is because of the reference collapsing rules (& + && = &): if T is MyType&, then T& and T&& are both MyType&, hence those two constructors have the same signature here.

But is there any nice way to overcome this, and allow T to be a reference, while still having both const T& and T&& constructors?

Andrii Zymohliad
  • 1,237
  • 1
  • 11
  • 17

1 Answers1

2

You can use a templated constructor with a forwarding reference to cover both cases:

template<typename T>
struct Result {
    template<typename... S>
    Result(S&&... s) : t(std::forward<S>(s)...) {}

    T t;
};

int i;
Result<int&> r1(i);
Result<int> r2(i);

Related papers on std::expected<R, E> proposal:

Evg
  • 25,259
  • 5
  • 41
  • 83
  • And so SFINAE to cover `T` vs `E`. – Jarod42 Jun 11 '19 at 13:24
  • @Jarod42, shouldn't it be a tag that distinguishes between constructors? What if `T` and `E` are the same type? – Evg Jun 11 '19 at 13:29
  • Make it `template Result(S&& ... s)` while you're at it. – n. m. could be an AI Jun 11 '19 at 13:35
  • From class purpose T != E, as T is the return type, and E is the error type. – Jarod42 Jun 11 '19 at 14:21
  • Thanks! A lot of things for me to learn and understand here. What is this `...` syntax with template arguments? This answers the question, but unfortunately I'm still unable to compile it, since my members are defined as: `union { T value; E error }`, and when I make `T` a reference, I get this: `error: union member 'value' has reference type 'MyType &'`. Nevertheless, it is another issue. Thank you! – Andrii Zymohliad Jun 11 '19 at 15:18
  • 1
    @AndriiZymohliad, it's an argument pack, so that you can write `Result(a, b, c, ...)` with an arbitrary number of arguments and pass all of them to the constructor of `T`: `t(a, b, c, ...)`. To put a reference into a union, you can use `std::reference_wrapper`: https://stackoverflow.com/questions/38691282/use-of-union-with-reference – Evg Jun 11 '19 at 15:24
  • @Evg, thanks. So as I understand, to make it generic over references and non-references, I should somehow detect if `T` is a reference, and then only if it is create `std::reference_wrapper` around it? – Andrii Zymohliad Jun 11 '19 at 15:30
  • @Jarod42, conceptually `T` might be the same as `E` (e.g. success value is some string, and error is the error message, so both would be strings). So to avoid any collisions here I made constructors only for success objects, and error objects are created with a static function Result::Error(). – Andrii Zymohliad Jun 11 '19 at 15:34
  • 2
    @AndriiZymohliad, one possible solution is to make it always hold a non-reference, and then if someone wants to put a reference into `Result`, he uses `std::reference_wrapper` himself. – Evg Jun 11 '19 at 15:34
  • 1
    @AndriiZymohliad, also take a look at this paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4109.pdf – Evg Jun 11 '19 at 15:36