2

Consider the following templated function:

template <template<class, class> class V, class T, class Allocator>
void fn(V<T, Allocator>& v)
{
  for (T& t : v) {
    std::cout << t << std::endl;
  }
}

When I pass it a non-const vector like:

std::vector<int> v{ 1, 2, 3, 4, 5 };
fn(v);

The compiler is happy, and I am happy. However, when I pass fn() a const vector like:

const std::vector<int> v{ 1, 2, 3, 4, 5 };
fn(v);

All hell breaks loose.It will result in a compilation error. With GCC 4.9.2, the error message is:

 In function 'int main()':
17:7: error: invalid initialization of reference of type 'std::vector<int>&' from expression of type 'const std::vector<int>'
7:6: note: in passing argument 1 of 'void fn(V<T, Allocator>&) [with V = std::vector; T = int; Allocator = std::allocator<int>]'

I thought that V<T, Allocator>& will be deduced to const std::vector<int, std::allocator<int>>&. However, this is not the case. Why is this so?

Sean Francis N. Ballais
  • 2,338
  • 2
  • 24
  • 42
  • 2
    If a function declaration expect a non-const that means it want to mutate it. It make sense the compiler don't allow this. It is a rule of thumb to pass argument as a const reference/pointer if your function don't mutate it. – UltimaWeapon Dec 31 '20 at 09:39
  • 2
    The function accepts the argument by non-`const` reference, which means (for the caller) that the function may change its argument. A `const` object is one that is intended to not be (logically) changed. Passing an object that is intended to remain unchanged to a function that may change it is invalid - hence the diagnostics in the second case. – Peter Dec 31 '20 at 09:40
  • I'm with ^^. And as you're not modifying the objects, you should as well use `for (T const& t : v)`. (And `std::endl` causes a buffer flush, so you can replace that with `'\n'` for better performance).... but it is interesting why the const-qualification of V is dropped... – JHBonarius Dec 31 '20 at 09:42
  • This is a dupe of [Error passing arguments to template function with const qualifier](https://stackoverflow.com/q/19036424), but I'm not satisfied with the answer there. – JHBonarius Dec 31 '20 at 09:47
  • 1
    @JHBonarius That's a good dupe. Why don't you like the answer? – super Dec 31 '20 at 09:52
  • @UltimaWeapon, such behaviour strikes to me as odd. When I'm using a similar templated function, `template void fn(T& t) { ... }`. and passing it a variable of primitive type, even with `const`, I get no error. I would presume this to be due to differing template deduction rules. – Sean Francis N. Ballais Dec 31 '20 at 09:55
  • 1
    Does this answer your question? [Error passing arguments to template function with const qualifier](https://stackoverflow.com/questions/19036424/error-passing-arguments-to-template-function-with-const-qualifier) – super Dec 31 '20 at 09:57
  • @super because it's specifically about `std::auto_ptr`, quote _"there's your problem: `T` can not be `const std::auto_ptr` itself, as it combined a type-property `const` with a template `std::auto_ptr` which is not a valid type"_ – JHBonarius Dec 31 '20 at 10:01
  • @JHBonarius Uhm. It's not specifically about `std::auto_ptr` at all. It literally says *as it combined a type-property const with a template* in that same line of text... which is the exact same issue as here. – super Dec 31 '20 at 10:03
  • @super you're right. I now also voted close dupe. at the TS: why are you using gcc 4.9.2? That's over 6 years old! We're at version 10.2, going to 11.0 now. – JHBonarius Dec 31 '20 at 10:07
  • @JHBonariusm, since I was just creating an MRE, I decided to use [cpp.sh](http://cpp.sh). It just happens that it uses GCC 4.9.2. – Sean Francis N. Ballais Dec 31 '20 at 10:10

1 Answers1

3

I thought that V<T, Allocator>& will be deduced to const std::vector<int, std::allocator<int>>&

This would be true if the parameter was plain U, like template<class U> void fn(U&);.

Type deduction is pattern matching. const is part of the type, hence the pattern U can be deduced to T const. Consider:

using T = int const;
T var;

template<class U>
void fn(U&);

fn(var); // -> fn<int const>(int const&);
// We deduce U = T = int const

Type aliases are transparent, so we never actually deduce T. The constness however is still part of the type of var. The compiler then "invents" a new type alias in form of the template parameter, as if:

void fn<int const>(int const&)
{
    using U = int const;
    // ...
}

However, the pattern V<X> is a template instantiation, not a any type. In the end, we need to make the function prototype compatible to the call site. So we need to deduce V such that we get void fn(vector<int> const);. This would only work if we deduce V to be a metafunction:

template<class U>
using M = vector<U> const;

template<template<class> class V, class T>
void fn(V<T>&);

M<int> var;
fn(var); // ?? fn<M, int>(M<int>);

As with type aliases, alias templates are transparent - they're never deduced themselves. The second you write M<int> it gets resolved to vector<int> const and we forget about the alias template/metafunction being in use.

The compiler could "invent this metafunction" when it sees the pattern V<T> and the type vector<int> const, but I think this "inventing of the metafunction" is just not supported. Neither is augmentation of metafunctions like vector const (without template arguments, to keep it an unapplied metafunction). Metafunctions are not really a thing in the language, we deal with transparent alias templates (which are never deduced) and actual class templates which don't support the notion of adding/removing const (they don't deal with qualifiers, just classes).

If it were supported, it could behave as if:

void fn<..>(vector<int> const&)
{
    template<class T>
    using U = vector<T> const;
    // ...
}
dyp
  • 38,334
  • 13
  • 112
  • 177
  • This might be a result of alias templates coming into play only with C++11... the deduction rules had already been written by that point. – dyp Dec 31 '20 at 10:05