Search code examples
c++templatesc++17variadic-templatesvariant

Store an lvalue or an rvalue in the same object using variants


I am reading a technique that is used to store an lvalue or an rvalue in the Same Object. Please find the description of this here. The overloaded trick is implemented.

However when the overload struct is defined , an addition overload constructor is defined, in which the function pointers that are produced when the lambdas are decayed, are passed as arguments

template<typename... Functions>
struct overload : Functions...
{
    using Functions::operator()...;
    overload(Functions... functions) : Functions(functions)... {}
};

Why is it needed to explicitly define this constructor?

Here a live demo.

Analysing the code using the cppinsights the template instantiation for the above piece of code is the following

inline overload(__lambda_39_13 __functions0, __lambda_40_13 __functions1, __lambda_41_13 __functions2)
  : __lambda_39_13(__functions0)
  , __lambda_40_13(__functions1)
  , __lambda_41_13(__functions2)
  {
  }

It is not clear to me how this template instantiation is generated .

Using explicitly custom deduction guide does not compile

Have a look in the following modified demo

The following error is produced

:38:9: error: no matching function for call to 'overload&) [with T = ...

Solution

  • You are trying to initialize overload via parenthesized initialization, e.g. here:

    overload(
        [](Value<T> const& value) -> T const&             { return value.value_; },
        [](NonConstReference<T> const& value) -> T const& { return value.value_; },
        [](ConstReference<T> const& value) -> T const&    { return value.value_; }
    )
    

    In C++17 parenthesized initialization with two or more arguments always requires a constructor to be available accepting the arguments in the initializer. If you don't declare any constructor in overload then that can't possible work since implicitly declared constructors can only be the default, copy or move constructors, none of which accepts three arguments. You are also not inheriting any constructors from overload's base classes (and even if you were that would not help you).

    The deduction guide can't change the fact that there is no constructor to choose, no matter how the template arguments are decided.

    However, if the constructor is not declared, then overload is an aggregate class and you could use aggregate initialization instead of initialization by a constructor. Aggregate initialization will not call any constructor, but instead initializes the base class subobjects (followed by direct member subobjects if there were any) one-by-one in order from the initializers in the initializer list. This requires only that you use braces instead of parentheses in C++17. Aggregate initialization was not considered with parentheses in C++17:

    overload{
        [](Value<T> const& value) -> T const&             { return value.value_; },
        [](NonConstReference<T> const& value) -> T const& { return value.value_; },
        [](ConstReference<T> const& value) -> T const&    { return value.value_; }
    }
    

    Unfortunately in C++17 there was no automatic deduction guide for aggregate initialization, so you will have to still add the deduction you suggested in your modified demo.

    If you were using C++20 (and a compiler fully implementing it), then you would need neither the deduction guide nor the braces to make use of aggregate initialization here, since a sufficient automatic deduction guide for aggregate initialization was introduced and aggregate initialization with parentheses was made possible. The original would just work without having to define the constructor.