Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template Template Arguments Inconsistent with Deduced Template Arguments

The following code fails to compile with the error template template argument has different template parameters than its corresponding template template parameter:

#include <tuple>
#include <vector>
#include <string>
#include <iostream>

template<template<typename, typename> class Blah, typename KeyType, typename ValueType>
void print_tuploid(const Blah<KeyType, ValueType>& tup) {
    std::cout << "first value: " << std::get<0>(tup) << " second value: " << std::get<1>(tup) << std::endl;
}

int main() {
  auto stuff = std::make_tuple(1, 2);
  print_tuploid(stuff);
}

The original intention behind this code is irrelevant, at this point I'm just trying to understand why this is considered to be invalid.

If I change the call to std::make_tuple into std::make_pair, the code compiles and runs correctly, which leads me to believe there's something weird going on that's specific to std::tuple.

I was originally thinking that std::tuple might have some extra, defaulted, template arguments I wasn't aware of, because if I change the definition of print_tuploid to the following, the code DOES compile for both std::make_tuple and std::make_pair:

template<template<typename...> class Blah, typename KeyType, typename ValueType>
void print_tuploid(const Blah<KeyType, ValueType>& tup) {
    std::cout << "first value: " << std::get<0>(tup) << " second value: " << std::get<1>(tup) << std::endl;
}

But when I tried to dump out the deduced type of stuff using the following code:

#include <tuple>

template<typename T>
class TypePrinter;

int main() {
  auto stuff = std::make_tuple(1, 2);
  TypePrinter<decltype(stuff)> error;
}

It reported: implicit instantiation of undefined template 'TypePrinter<std::__1::tuple<int, int> >', leading me to believe this is not the case.

Also, as a side question, why does reference collapsing not occur in this context? The following code is also considered invalid:

#include <iostream>

template<template<typename, typename> class Blah, typename KeyType, typename ValueType>
void print_tuploid(Blah<KeyType, ValueType>&& tup) {
    std::cout << "first value: " << std::get<0>(tup) << " second value: " << std::get<1>(tup) << std::endl;
}

int main() {
  auto stuff = std::make_pair(1, 2);
  print_tuploid(stuff);
}

Giving the error: no known conversion from 'std::__1::pair<int, int>' to 'pair<int, int> &&' for 1st argument.

Basically I'm just trying to extend my template knowledge by understanding what exactly is going on here. Sorry for the long post, and thanks in advance for any guidance anyone is able to provide.

like image 578
Chris Fretz Avatar asked Aug 17 '17 02:08

Chris Fretz


People also ask

What is template argument deduction?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.

What are template arguments?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

Can a template be a template parameter?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

What is Typename C++?

" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.


1 Answers

The issue with the function is that it matches against a template class that takes exactly 2 arguments. In reality, std::tuple has the template signature of template <typename...>. The distinction lies in that tuple takes an arbitrary amount of template parameters, while your function expects 2.

std::pair works fine because the template signature is template <typename, typename> which matches exactly.

There is a proposal, although I am not able to find the latest version, that enables this sort of specialization. It seems to have passed into C++17, or at least something similar to it, as the cppreference page for template template parameters considers this a valid example. The proposal I found is here.

The reason why your second version does not accept std::pair is because it expects an rvalue reference. Once you have a (partially) specialized type, you cannot treat && as a forwarding reference.

like image 132
user975989 Avatar answered Oct 06 '22 15:10

user975989