1

I tested std::forward with CRTP(Curiously Reccursive Template Pattern). However I cannot pass lvalue to it. Please read the following code.

#include <algorithm>

struct DataClassA {};

template <typename Derived>
struct ContainerCRTP {
    template <typename Element>
    void add(Element&& tag) { }
};

struct Container : public ContainerCRTP<Container> { };

int main()
{
    DataClassA data_a;
    Container container;

    // Case 1: passing lvalue. compile error.
    container.add<DataClassA>(data_a);

    // Case 2: passing rvalue. It succeeds compiling.
    container.add<DataClassA>(std::move(data_a));                
}

When I pass lvalue in Case 1, gcc compiler gives me a compile error,

template argument deduction/substitution failed:
cannot convert ‘data_a’ (type ‘DataClassA’) to type ‘DataClassA&&’

On the other hand, when I pass rvalue in Case 2, gcc compiler does not give me compiler error.

How can I give lvalue to container.add<DataClassA>(data_a) ?

M.M
  • 138,810
  • 21
  • 208
  • 365
mora
  • 2,217
  • 4
  • 22
  • 32
  • And what exactly `container.template` is? – user7860670 May 15 '17 at 10:29
  • @VTT `template` is a keyword in C++, [see here](http://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords). I think it is redundant in `TestStdForward2` tho – M.M May 15 '17 at 10:34
  • 2
    Why are you actually deriving from std::vector in the first place? The code fails, since data_a is no temporary and your "add" requires an rvalue-ref, i.e. a ref to a temporary object... your best bet was to duplicate the above function and change one of the signatures to take a const Element&... This way you can store both types of objects. – MABVT May 15 '17 at 10:46
  • AND: Further reading, which explains things beautifully: http://thbecker.net/articles/rvalue_references/section_05.html – MABVT May 15 '17 at 10:50
  • @MABVT `add` takes a forwarding reference, not a rvalue-ref – M.M May 15 '17 at 10:52
  • 1
    When you say `add(data_a)` you have explicitly specified that `Element&&` is `DataClassA&&` (and not `DataClassA&`) . If you just do `container.add(data_a);` the type will be deduced correctly. However, the code *inside* the function doesn't work for that case as you cannot have `std::vector`. So follow MABVT's advice and make it two functions. – Bo Persson May 15 '17 at 11:00
  • @M.M ah... learned something new. OK the reason why deduction fails is, that you provide a type in brackets already, which seems to break deduction. Removing the value in brackets, though, will yield " `vector is not a parent of Container` ", since the forwarding-reference param will be deduced to `DataClassA&` ! – MABVT May 15 '17 at 11:03
  • I edited the code to contain a MCVE. The original code had other errors and red herrings. OP, please post the exact code you tried to compile next time. – M.M May 15 '17 at 11:19
  • Thank you for commenting and answering, everyone. I tried to simplify my code as possible as I can but I could not remove crtp part because I have expected the compile error might be related to crtp. I am sorry for giving you some noises and thank you again. – mora May 15 '17 at 11:33

1 Answers1

3

When you call:

container.add<DataClassA>(data_a);
//            ^^^^^^^^^^^

explicitly providing the template parameter means that the compiler does not deduce the template parameter (i.e. you do not get forwarding reference behaviour).

Instantiating add with DataClassA as template parameter means its signature is:

void add(DataClassA&& tag) 

which is an rvalue reference, not a forwarding reference, therefore an lvalue argument cannot bind to it.

To use perfect forwarding you must let the compiler deduce the template parameter from the argument. There are three possible deductions (T, T&, T&&) based on the value category of the argument and this is how std::forward reproduces the value category of the argument:

container.add(data_a);
M.M
  • 138,810
  • 21
  • 208
  • 365