Skip to content
Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

C++17 Class Templates: Deduced or Not?

elbeno, 15 June, 201716 June, 2017

C++17 introduces class template deduction: a way for the compiler to deduce the arguments to construct a class template without our having to write a make_* function. But it’s not quite as straightforward as it seems.

Imagine we have a simple type that will tell us when it’s copied or moved, just for testing.

struct S
{
  S() = default;
  S(const S&) { std::cout << "copy\n"; }
  S(S&&) { std::cout << "move\n"; }
};

And likewise a very simple template like so:

template 
struct Foo
{
  Foo(const T& t_) : t(t_) {}
  Foo(T&& t_) : t(std::move(t_)) {}

private:
  T t;
};

Note that we provide two forms of constructor to deal with both rvalues and lvalues being passed in. With C++14, prior to class template deduction, we would write a make_foo function to instantiate this template something like this:

template 
auto make_foo(T&& t)
{
  return Foo>(std::forward(t));
}

And call it like so:

int main()
{
  // rvalue: move
  auto f1 = make_foo(S());

  // lvalue: copy
  S s;
  auto f2 = make_foo(s);
}

The important thing here is that the template argument to make_foo is deduced, and the template argument to Foo is not (cannot be, in C++14). Furthermore, because the template argument to make_foo is deduced, make_foo's argument is a forwarding reference rather than an rvalue reference, and hence we use perfect forwarding to pass it along to the appropriate constructor for Foo.

So far so good. Now, with C++17, class template deduction is available to us, so we can get rid of make_foo. Now our main() function looks like this:

int main()
{
  // rvalue: move?
  Foo f3{S()};

  // lvalue: copy?
  S s;
  Foo f4{s};
}

Here's the unintuitive part: the template arguments are being deduced now. But in that case, doesn't that mean that Foo's constructor argument is being deduced? Which means that what looks like Foo's rvalue constructor is actually now a constructor with a forwarding reference that will outcompete the other constructor? If so, that would be bad - we could end up moving from an lvalue!

I woke up the other morning with this worrying thought and had to investigate. The good news is that even though the code looks like it would break, it doesn't.

So in fact, yes, although Foo's template argument is being deduced, I think the crucial thing is that Foo's constructor still takes an rvalue reference - not a forwarding reference - at the point of declaration. And according to 16.3.1.8 [over.match.class.deduct] the compiler is forming an overload set of function templates that match the signatures of the constructors available, and it's using the correct types. In other words, I think it's doing something that we could not do: it's forming a function template, for the purposes of deduction, whose argument is an rvalue reference rather than a forwarding reference.

As is often the case in C++, one needs to be careful to properly distinguish things. It is very easy to get confused over rvalue references and forwarding references, because they look the same. The difference is that forwarding references must be deduced... and in this case, even though it looks like it's deduced, it isn't.

Edit: Reddit user mps1729 points out that indeed, neither of the implicitly generated functions is using a forwarding reference, as clarified in 17.8.2.1/3 [temp.deduct.call]. Thanks for the clarification!

C++

Post navigation

Previous post
Next post

Related Posts

Remember the Vasa! or, plus ça change, plus c’est la même chose

12 August, 201915 September, 2021

I’ve been programming in C++ for almost a quarter of a century now. I grew up, professionally, with C++, and in many ways, it grew up along with me. For someone who is used to C++, even used to recently-standardised C++, it’s hard not to feel apprehension when looking at…

Read More

10 Non-C++ Book Recommendations for C++ Programmers

19 January, 2018

So you’ve learned enough C++ to function. You’ve read a bunch of the usual recommendations: Meyers, Stroustrup, maybe even Alexandrescu and Stepanov. You know enough to recommend Lippman et al. to newbies rather than the other “C++ Primer.” The internet has lots of C++-related book recommendations to make — for…

Read More

Clang/GCC weirdness

5 February, 201530 June, 2015

Consider the following code: #include #include using namespace std; // base template template struct what_type { void operator()() { cout

Read More

Comments (3)

  1. thewisp says:
    16 June, 2017 at 1:52 am

    Rvalue and forwarding reference are indeed difficult to distinguish.

    The other day I had a problem with forwarding function parameters, whose (decayed) types are part of class template arguments.

    template class MulticastDelegate;
    
    template
    class MulticastDelegate
    {
        //can't perfect forward here!
        //void operator()(ArgsT&& ... args); //&& means rvalue
        void operator()(ArgsT ... args);
    
        //This would be one way of solving the problem
        //template
        //void operator()(OtherArgsT&& ... args); //&& means forwarding
    };
    

    I wonder if there is a nice solution other than making the function templated?

  2. thewisp says:
    16 June, 2017 at 1:54 am

    Sorry for the swallowed brackets,
    https://godbolt.org/g/yb8Cjj

  3. elbeno says:
    16 June, 2017 at 8:11 am

    Yes, I’ve accidentally had the same issue: it’s easy to forget/not notice that template arguments aren’t deduced and therefore aren’t forwarding references.

    Brackets fixed!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

©2026 Why is a raven like a writing desk? | WordPress Theme by SuperbThemes