Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do template template parameters with constraints require stricter arguments?

N3580 describes the following scenario:

template<Object T, template<Object> Cont>
struct stack {
    Cont<T> container;
};

template<Object>
struct my_vector;

template<Regular>
struct my_list;

template<typename>
struct my_magic;

Here, Regular is a refinement of Object; that is, every Regular is an Object but not every Object is a Regular.

I would expect the type system to be such that for stack<X, Y> to be valid, X must be an Object and Y must be instantiable with an Object. This would mean that stack<int, my_vector> and stack<int, my_magic> are valid, while stack<int, my_list> is not. Much like the case with normal functions:

struct Base {};
struct Derived : Base {};

void foo(Base* p, function<void(Base*)> fun) {
    fun(p);
}

template<typename T>
void bar(T*);

I would expect that if p is a Base*, then foo(p, bar<Base>) and foo(p, bar<void>) are valid, while foo(p, bar<Derived>) is not; after all, a Base* has an implicit conversion to a void*, but not to a Derived*.

In the case of templates, though, the situation is the opposite. Only stack<int, my_vector> and stack<int, my_list> are allowed, while stack<int, my_magic> is forbidden. Why is this? my_magic works fine with any type, while my_list may fail depending on what object I give it. Moreover, I can trivially make my_magic work with only objects:

template<Object T>
struct my_restricted_magic : my_magic<T> {};

Now my_restricted_magic can be used with stack. On the other hand, there's no easy way of making a my_list which accepts any type, but this is exactly what passing it as a template template parameter now allows.

Am I misinterpreting the purpose of constraints on template template parameter's parameters?

like image 662
Anton Golov Avatar asked Dec 30 '13 22:12

Anton Golov


2 Answers

It's a bug in the proposal. A constrained template template parameter should accept arguments with weaker constraints.

like image 50
Andrew Sutton Avatar answered Jan 01 '23 19:01

Andrew Sutton


This is conjecture, but it seems like a likely explanation:

Allowing to pass a more specific template, rather than a more generic one, is the way the rules currently work in regard to variadic template template parameters. You are allowed to pass a single-type-parameter template where a variadic is expected:

template<template<typename...> class> struct Foo {};
template<typename> Bar {};

Foo<Bar>(); // legal

But not vice-versa:

template<template<typename> class> struct Foo {};
template<typename...> Bar {};

Foo<Bar>(); // error, argument/parameter mismatch.

The phrasing originated in N2555 where it was requested to allow code like this:

template<typename>
struct Foo;

template<template<typename...> class Fun, template... Args>
struct Foo<Fun<Args...>> {};

Foo<std::pair<int, double>>();

Basically, instead of the template<typename...> class being a guarantee to the user, it is a catch-all thing that the user must then provide sensible arguments for. Given this usage involving specialisations, that does seem reasonable.

This does not explain why passing a more general template is not allowed, but reversing the phrasing in N3580 would make the two rules put together rather unintuitive.

Proposal link: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2555.pdf‎

like image 35
Anton Golov Avatar answered Jan 01 '23 19:01

Anton Golov