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

Thoughts on Default Construction

16 August, 2017

What does default construction mean? Why do we write default constructors? When and why should we require them? I’ve been pondering these questions lately. One of the great things that C++ gets right is that it grants programmers the ability to create types that behave like built-in types. Many languages…

Read More

Experimenting with constexpr

13 October, 201515 October, 2015

Since seeing Scott Schurr at C++Now in Aspen and hearing his talks about constexpr, it’s been on my list of things to try out, and recently I got around to it. With the release of Visual Studio 2015, Microsoft’s compiler now finally supports C++11 style constexpr (modulo some minor issues),…

Read More

Floating-point maths, constexpr style

13 October, 201515 October, 2015

(Start at the beginning of the series – and all the source can be found in my github repo) To ease into constexpr programming I decided to tackle some floating-point maths functions. Disclaimer: I’m not a mathematician and this code has not been rigorously tested for numeric stability or convergence…

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