oisyn
oisyn

Reputation: 1376

Detecting actual arity of template template argument

I'm fiddling around with template metaprogramming, specifically with type sequences and STL-like algorithms working on those sequences. One thing I ran into was the transformation of predicates, for example by binding one of their parameters

I think it's hard to describe my problem without first providing some background information. Here's an example:

#include <type_traits>

// Type sequence
template<class... T>
struct typeseq
{
    static constexpr size_t Size = sizeof...(T);
};

// Some algorithm
template<class TS, template<class...> class P>
using remove_if = /* ... some template logic ... */;

// Usage example:
using a = typeseq<int, float, char*, double*>;
using b = remove_if<a, std::is_pointer>;   // b is typeseq<int, float>

As shown here, remove_if requires a predicate that serves as an oracle for the removal algorithm to determine which of the elements to remove. As we're dealing with metaprogramming, it is in the form of a template template parameter. ( Note that P is using a variadic template parameter here. Although I'm expecting a unary template, an earlier version of C++ had the restriction that a variadic template argument can't be used as a non-variadic template parameter, which severely restricts predicate transformations. ) It requires that the predicate, when instantiated, resolves to a type that has a nested compile time value member that is convertible to bool.

All is well, but say you want to remove every type that is convertible to int. Obviously, std::is_convertible is a binary predicate, but remove_if above requires a unary predicate. We just need to fix the second template argument to int. Let's define a bind2nd:

template<template<class, class...> class P, class BIND>
struct bind2nd
{
    template<class T1, class... Tn> using type = P<T1, BIND, Tn...>;
};

// and now we should be able to do:
using c = remove_if<ts, bind2nd<std::is_convertible, int>::type>;   // c is typeseq<char*, double*>;

Unfortunately, this fails to compile on latest Clang and MSVC++. Apparently, the issue is the pack expansion of Tn... into std::is_convertible<T1, BIND, Tn...> while std::is_convertible only has 2 template parameters. It doesn't seem to matter that the pack is empty in practice (isolated example)

I'd rather not provide 'overloads' for any required arity of the predicate passed into bind2nd. Is there a way to detect the arity of P in bind2nd above? GCC allows me to partially specialize it for non-variadic versions of P:

template<template<class, class> class P, class BIND>
struct bind2nd<P, BIND>
{
    template<class T1> using type = P<T1, BIND>;
};

But unfortunately GCC wasn't the one that was complaining in the first place. I also doubt how conformant such a partial specialization is. Is there a way around this? Is it possible to detect the actual arity of a template template parameter, and do something different based on that information?

Upvotes: 4

Views: 210

Answers (1)

oisyn
oisyn

Reputation: 1376

I think I found a workaround.

The problem seems related to type aliases - they seem to directly pass along any template arguments, rather than instantiating the type as is the case with a class or struct. We can use this in our favor, by using a struct as an intermediate step:

template<template<class, class...> class P, class BIND>
struct bind2nd
{
    template<class... Tn>
    struct impl
    {
        using type = P<Tn...>;
    };
    template<class T1, class... Tn> using type = typename impl<T1, BIND, Tn...>::type;
};

Now it works :). It's a bit convoluted though. I wonder if this is all according to standard, but it seems to compile on all major compilers.

Edit: Why am I complicating things by using nested types and aliases? I can just as well use derivation for predicates:

template<template<class, class...> class P, class BIND>
struct bind2nd
{
    template<class T1, class... Tn>
    struct type : P<T1, BIND, Tn...> { };
};

Clean and simple. And it makes it almost identical to the first definition of bind2nd in the OP.

Upvotes: 3

Related Questions