0

Can't figure out what is the difference between h1 and h2 in the code below:

#include <utility>

template <class Func>
struct Holder
{
    Holder(Func && func) : m_func(std::forward<Func>(func))
    {
    }

    Func m_func;
};

template <class Func>
auto MakeHolder(Func && func)
{
    return Holder<Func>(std::forward<Func>(func));
}

int main()
{
    auto func = [](int val) {};

    Holder h1 = MakeHolder(func);

    Holder<decltype(func)> h2(func);

    return 0;
}

Why h1 compiles, but h2 does not? The error with GCC is

prog.cc:25 : 31 : error : cannot bind rvalue reference of type 'main()::<lambda(int)>&&' to lvalue of type 'main()::<lambda(int)>'
    25 | Holder<decltype(func)> h2(func);

Are types Func in the function template arguments and decltype<func> in main() different?

Dmitriano
  • 1,878
  • 13
  • 29
  • The error message says it. In your ctor you accept an argument of type `Func &&` yet `func` is an lvalue of type `Func`. – bipll Oct 03 '20 at 21:45
  • Why are your ctor taking a forwarding reference anyway? – einpoklum Oct 03 '20 at 21:46
  • I think the reason for the difference is because a function does template type deduction, whereas in the class version, you're explicitly stating it (incorrectly in this case). – Ami Tavory Oct 03 '20 at 21:47
  • @einpoklum Did you mean why do I use `std::forward` in ctor? – Dmitriano Oct 03 '20 at 21:47
  • @AmiTavory are `Func` and `decltype` different types? – Dmitriano Oct 03 '20 at 21:51
  • @Dmitriano an l-value to func, is not the same as an rvalue to it, for example, similar to how const reference to func is not exactly a func. I'd guess that the reason for the difference is a combination of template function type resolution + some rules (which I don't remember) about what rvalues to other types of values become. I'm less into C++ these days. – Ami Tavory Oct 03 '20 at 21:55
  • 1
    @Dmitriano: 1. Yes, no need for the forwarding reference and std::forward in the ctor - you're not forwarding to some other templated function. – einpoklum Oct 03 '20 at 21:57
  • 1
    The compilation error is due to `&&` being overloaded, depending on context, as either an rvalue reference or a universal reference. It is an rvalue reference in the constructor, and ***not*** a forwarding reference. – Sam Varshavchik Oct 03 '20 at 22:00
  • I think this question is more specific than the dupe target. It's asking about specifically how decltype works here. Voting to reopen. – cigien Oct 03 '20 at 22:17
  • @SamVarshavchik So the function parameter is of type `& &&` and the constructor parameter is of type `&&` right? – Dmitriano Oct 03 '20 at 22:40
  • I don't know what "type `& &&`" means. There is no such "type" in C++. – Sam Varshavchik Oct 03 '20 at 23:21
  • @SamVarshavchik what do you know then? – Dmitriano Oct 04 '20 at 00:27
  • What I know are the things that are described in the duplicate question. – Sam Varshavchik Oct 04 '20 at 00:41

1 Answers1

0

The difference is that the Holder class is instantiated with different types for Func:

  1. For h1, it's main()::<lambda(int)>&
  2. For h2, it's main()::<lambda(int)>

You see, when you have a variable of type T and use it, the type of the use is not the same as that of the variable: T& instead of T. But in decltype() the semantics are different.

Anyway, you can get your code to compile like so:

#include <utility>

template <class Func>
struct Holder
{
    Holder(Func func) : m_func(func) { }

    Func m_func;
};

template <class Func>
auto MakeHolder(Func && func)
{
    return Holder<Func>(std::forward<Func>(func));
}

int main()
{
    auto func = [](int) {};
    [[maybe_unused]] Holder h1 = MakeHolder(func);
    [[maybe_unused]] Holder<decltype(func)> h2(func);
}

GodBolt

Or if you really must - use std::move(func) in the initializer list. But - I'm not sure I would keep a Func member, for arbitrary Func (e.g. when it's a reference or a pointer to something temporary that may go away.

einpoklum
  • 118,144
  • 57
  • 340
  • 684