Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a template<typename...> not recognized as instantiatable through template<template<typename> typename>?

I'm trying to arbitrarily "bind" template parameters but have run into an elegance problem.

To cut straight to the underlying problem, gcc 6.2 has a problem with the following but logically I see no issue with it...

template<template<typename, typename> P, typename A, typename B>
struct foo {
    static constexpr bool value = P<A, B>::value;
};

template<typename...Ts>
struct bar {
    static constexpr bool value = true;
};

... foo given bar such as foo<bar, void, void> should result in the instantiation of bar<void, void> (which is valid), who's value member is true and therefore foo<bar, void, void>::value is likewise true. Effectively, this should (in my mind) result in instantiated structs conceptually akin to...

struct bar<void, void> {
    static constexpr bool value = true;
};

struct foo<bar, void, void> {
    static constexpr bool value = bar<void, void>::value; //which is true
};

You can see this concept in action (or rather the error) here https://godbolt.org/g/lT9umg.

Back to the begining now, first I tried the following...

template<typename...>
struct type_list { };

template<template<typename...> typename Tmpl, typename...Ts>
struct bind_template {
    template<typename...Us>
    using type = Tmpl<Ts..., Us...>;
};

template<template<typename> typename Predicate, typename...Ts>
struct has_matching_type {
    private:
        template<template<typename> typename, typename, typename=void>
        struct helper: std::false_type { };
        template<template<typename> typename P, typename U, typename...Us>
        struct helper<P, type_list<U, Us...>, typename std::enable_if<P<U>::value>::type>: std::true_type { };
        template<template<typename> typename P, typename U, typename...Us>
        struct helper<P, type_list<U, Us...>, typename std::enable_if<!P<U>::value>::type>: helper<P, type_list<Us...>> { };
    public:
        static constexpr bool value = helper<Predicate, type_list<Ts...>>::value;
};

template<typename T, typename...Ts>
using has_type = has_matching_type<bind_template<std::is_same, T>::template type, Ts...>;

Later I might try to instantiate through has_type<T, Ts...> such as...

cout << has_type<long, int, bool, long, float>::value << endl;

However, as I pointed out gcc 6.2.0 complains because it doesn't seem to recognize that, once resolution is done, the template instantiation is pragmatically equivalent.

Simply knowing the number of template parameters and specializing for that exact number solves the problem. If I specialize bound_template keeping std::is_same<LHS, RHS> in mind...

template<template<typename, typename> typename Tmpl, typename T>
struct bind_template<Tmpl, T> {
    template<typename U>
    using type = Tmpl<T, U>;
};

... we suddenly compile and evaluate compile-time no problem because gcc sees bind_template<std::is_same, long>::type as taking one type parameter exactly.

Obviously, abstracting this concept out to allow for any template parameters, such as integral constants and not just types, is a fundamental issue regardless of compiler. Simply focusing on types for a minute though, my question is multiple:

  1. Am I missing something here conceptually and the compiler is actually doing exactly what should be obvious to me?
  2. If not, is this in violation of C++11 standards, not directed by standards, or is it a compiler dependency?
  3. Is there some elegant way I can get around this regardless of the answers to my first two questions?

Functionally, the real question (particularly if this is an unavoidable issue in C++11) is...

Is there some elegant way I can abstractly bind templates without having to specialize for every case (the simplest here being n number of types)?

Just being able to get an answer to questions 1 or 3 would be great. Question 3 is the most important because at the end of the day it's what works that matters.

Obviously, I can specialize (as shown above). A big problem though is that even the following doesn't seem to work (at least according to this online compiler)...

template<template<typename...> class Tmpl, typename... Ts>
struct bind_helper {
    template<typename... Us>
    struct type: Tmpl<Ts..., Us...> { };
    template<typename A>
    struct type<A>: Tmpl<Ts..., A> { };
    template<typename A, typename B>
    struct type<A, B>: Tmpl<Ts..., A, B> { };
    template<typename A, typename B, typename C>
    struct type<A, B, C>: Tmpl<Ts..., A, B, C> { };
};

This means that, not only would I have to generate a bunch of parameters, but I'd have to match the outer parameters as well via complete bind_template specialization. This quickly becomes (actually is) a binomial problem.

Extending this concept further (but still keeping to types), I was planning to next implement "placeholders" the same way std::bind uses placeholders (which would have worked rather elegantly because I would have just peeled and rejoined the list at the index). Obviously this is too much of a mess to proceed without a more abstract approach available.

like image 976
mr.stobbe Avatar asked Feb 05 '17 04:02

mr.stobbe


People also ask

What is typename in template?

In template definitions, typename provides a hint to the compiler that an unknown identifier is a type. In template parameter lists, it's used to specify a type parameter.

Which keyword is equivalent to typename in C++ template?

In the original C++ compilers before the first ISO standard was completed, the typename keyword was not part of the C++ language and Bjarne Stroustrup used the class keyword for template arguments instead.

What does typename do in C++?

A typename keyword tells the compiler that an identifier is a type (rather than a static member variable)

Where and why do I have to put the template and typename keywords?

The typename keyword before a dependant name specifies that it is the name of a type. Use the keyword typename only in template declarations and definitions provided you have a qualified name that refers to a type and depends on a template parameter.


2 Answers

This is fixed in C++17. Specifically, from 14.3.3 "Template template arguments", paragraph 3:

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template<class ... Types> class C { /* ... */ };
template<auto n> class D { /* ... */ };
template<template<class> class P> class X { /* ... */ };
template<template<class ...> class Q> class Y { /* ... */ };
template<template<int> class R> class Z { /* ... */ };

X<A> xa;            // OK
X<B> xb;            // OK
X<C> xc;            // OK
Y<A> ya;            // OK
Y<B> yb;            // OK
Y<C> yc;            // OK
Z<D> zd;            // OK

The relevant example here is X<C>. This works in g++ 7 with the flag -std=c++1z.

C++14 specifies that the example above is ill-formed:

template<class T> class A { /∗ ... ∗/ };
template<class T, class U = T> class B { /∗ ... ∗/ };
template <class ... Types> class C { /∗ ... ∗/ };
template<template<class> class P> class X { /∗ ... ∗/ };
template<template<class ...> class Q> class Y { /∗ ... ∗/ };
X<A> xa; // OK
X<B> xb; // ill-formed: default arguments for the parameters of a template argument are ignored
X<C> xc; // ill-formed: a template parameter pack does not match a template parameter

The change came in late 2016 from the paper DR: Matching of template template-arguments excludes compatible templates. The change was applied in this commit from November. The issue has been known by the committee since 1999 or before.

like image 170
jbapple Avatar answered Oct 14 '22 00:10

jbapple


Before C++17, this code is ill-formed as-is, due to an unfortunate language defect as mentioned in jbapple's answer.

A C++11-conforming solution would be to switch from using metafunctions everywhere to using metafunction classes everywhere. A metafunction class, from Boost.MPL's definition, would be a type that has a template alias named apply. Which we can invoke thusly:

template <class MFC, class... Ts>
using apply_t = typename MFC::template apply<Ts...>;

We can lift a template into a metafunction class via:

template <template <typename...> class Z>
struct quote {
    template <class... Args>
    using apply = Z<Args...>;
};

Then let's rewrite bind_template to take a metafunction class instead of a template:

template <class MFC, class... Ts>
struct bind_template {
    template <class... Us>
    using apply = apply_t<MFC, Ts..., Us...>;
};

And then rewrite has_matching_type to take a metafunction class instead of a template:

template<class Predicate, class... Ts>
struct has_matching_type {
private:
    template<class>
    struct helper: std::false_type { };

    template<typename U, typename...Us>
    struct helper<type_list<U, Us...>>
        : std::conditional<
            apply_t<Predicate, U>::value,
            std::true_type,
            helper<type_list<Us...>>
            >::type
     { };

public:
    static constexpr bool value = helper<type_list<Ts...>>::value;
};

template<class T, class... Ts>
using has_type = has_matching_type<bind_template<quote<std::is_same>, T>, Ts...>;

And now your initial has_type<long, int, bool, long, float>::value is true, even in C++11.

like image 45
Barry Avatar answered Oct 14 '22 01:10

Barry