Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Abstracting over types of non-type template parameters

Tags:

c++

templates

I would like to write a template that can deconstruct a type into a template with non-type template parameters along with its non-type template arguments. E.g., it would deconstruct Array<5> into template<int> Array and 5, but would work generically for any type of non-type template parameter (integral types, pointers, member pointers, etc.).

First try, with template specialization:

template<typename T> struct foo { enum { n = 1 }; };

template<int x> struct bar { enum { n = x }; };

template<typename T, template<T> class X, T x>
struct foo< X<x> > { enum { n = x }; }; // here x must be of integral type, but that's just for testing

int main(int, char**) { return foo< bar<16> >::n; }

Clang 3.1 says:

test145.cpp:6:8: warning: class template partial specialization contains a template parameter that can not be deduced; this partial specialization will never be used
struct foo< X<x> > { enum { n = x }; };
       ^~~~~~~~~~~
test145.cpp:5:19: note: non-deducible template parameter 'T'                     
template<typename T, template<T> class X, T x>
                  ^
1 warning generated.

Second try, with a function template:

template<typename T, T x> 
struct box 
{ 
    static constexpr T value() { return x; }
};

template<typename T, template<T> class X, T x>
box<T, x> foo(X<x>);

template<int> struct asdf { };

int main(int, char**) { return decltype(foo(*(asdf<9>*)0))::value(); }

Clang says:

test150.cpp:12:41: error: no matching function for call to 'foo'
int main(int, char**) { return decltype(foo(*(asdf<9>*)0))::value(); }
                                        ^~~
test150.cpp:8:11: note: candidate template ignored: couldn't infer template argument 'T'
box<T, x> foo(X<x>);
          ^
1 error generated.

GCC 4.7 says similar things.

Is this a fundamental limitation?

Bonus question: If it is, then is there any way at all to handle all of the infinite possibilities in a finite amount of code, even if it's less simple and generic code? (It gets difficult with e.g. pointers: for the same reason that you can't seem to write template<T> I don't think you could write template<T*> either.)

Please don't ask why I'm asking.

like image 605
glaebhoerl Avatar asked May 25 '12 18:05

glaebhoerl


2 Answers

The answer might come a little late...

Missed Try...

(see Correct Answer & C++17 Solution below)


This original answer is kept as a souvenir of my very first answer on SO.
Not exactly a fail, one would say. But rather, a first missed try... ;)
Now, jump to the next horizontal line...

I was searching for an answer to a related question when I came across this question. After having read it, I told myself: "Humm... That's something I already did. And it worked. How did I do it exactly?!". And then, I continued searching for the answer to my question...

Today I felt like I should take a little time to propose a solution to this problem (two actually).

As you already noticed, the problem comes from the fact that the compiler has no clue on how to deduce T. One can interpret the error message as "Please, give me a little help on that T".

The first version I did that was working had the specialization of foo deriving from a class similar to std::integral_constant. Making foo derive from std::integral_constant<T, x> might have had help the compiler to figure out the type of T. (or maybe MSVC -vs2019- had been a little nice with me)

Anyway, meanwhile I found better solution. And there should have no way for the compiler not to be able to deduce the type of T, because, there is no need of a typename T parameter for the type of x...


Here it is: (C++17 Solution)

template<typename T> struct foo {};

template<auto x, template<decltype(x)> class X>
struct foo<X<x>> {
    using            arg_type   = decltype(x);
    static constexpr arg_type n = x;
};

//template<int x> struct bar { enum { n = x }; };
template<int x> struct bar;

using bar_16            = foo<bar<16>>;
using bar_16_arg_t      = typename bar_16::arg_type; // int
constexpr auto bar_16_n = bar_16::n;                 // 16

Note that for this to work, it is not even needed for bar to be a complete type. A forward declaration (like in this example) is perfectly enough for the decomposition to work.

Enjoy...


Correct Answer

° Notes

  • This answers the question asked 9 years ago, and,
  • The solution proposed here uses only C++11 features.
  • This solution manages only integral types.
    (other type is let as an exercise for the reader)

    If your compiler supports C++17 features, the solution posted above should be preferred, as it manages not only integral types.

Only interested in a working code example?
jump to: "Working Solutions"

° Preamble

  • After my researches, it seems that a solution to this particular problem hasn't been found (or not published) until now. I thought I should go in a little more details about the "Whys" and "Hows". I hope it will be appreciated, but overall: useful...
  • I am currently in the process of writing a meta-programming library full of compile-time tools and features. I hope to be able to release it on GitHub anytime soon.
    (who knows) – Anyway...
  • What hasn't been my frustration when I realized that my first answer was correct only since C++17... – Not exactly "on time", one could say...:P
  • With all the machinery of "compile-time only" features I have in mind ATM, I was feeling like there should have had a means to do that 9 years ago.
  • I begun to think about how I would have had done that using only C++11 features and, after an hour or so, I found a working solution. (two in fact)
  • It took me a little more time to make it a usable solution. (two in fact)
  • And quite a bit more to right this post... :D

    After all, there might have compilers
    "just good enough" to understand C++11 only... :P

Obviously, as the set of features available at that time was narrower,
the solution found is "just a bit" more verbose... :D

° Searching Process

Firstly, one has to keep in mind that when a compiler outputs "cannot deduce"...
– It does not mean that there is an error (though there might have one).
– It rather means that the compiler is not as clever as one thinks it is.
– It means that one has to give a hand to the compiler for it to be able to do its job...

In clear?
– The compiler is kindly asking for you to do a part of its job.
– And there's good chances for you:

  • To end up doing most of the job yourself... :P

Here, the compiler says "cannot deduce the type of T".
Indeed, T is not used in the expression used as argument of the specialization of foo, and thus, it cannot be deduced from there...

One first has to do something to signify the relation between typename T and the value of x (which is of type T). What instantly comes to mind is that one needs a template similar to that of std::integral_constant which does exactly that. It encodes, into a new type, a value along with its corresponding type.

Disclaimer [! Warning! ]

  • People susceptible to develop allergic reactions at sight of uppercase letters within identifier names should not continue reading this post!

Nothing new until there?
Perfect! Here it is:

template<typename T, T V>
struct NonTypeParam { using Type = T; static constexpr T Value = V; };

Next one needs something to create instances of the NonTypeParam template with a value and its corresponding type...

  • It might be a template taking a type parameter.
  • This parameter would receive the type to decompose.
  • Then, one would have to specialize it somehow...

Let's give it a try, and start with:

template<typename T> struct Extract { using Result = void; };

To fully abstract the specialization of the Extract template, one would have to write something like:

template<typename T, T V, template<T> class C>
struct Extract<C<V>> { using Result = NonTypeParam<T, V>; };

Which leads to the same problem, because it is the same type of specialization used in the question. At that point, one has to remind what the compiler was unable to do. It "cannot deduce" what type the parameter T is supposed to alias in our specialization...

In fact, the message is misleading somehow, because T is not even part of the expression passed as argument of the specialization. Thus, the problem is not to attribute a typename to the parameter T, it is rather to attribute a type to the parameter V...
Now, one should be able to ask the right questions:

  1. How can T be removed from the equation?
    • By explicitly defining the type of V.
  2. What are the possible types for the value of V?
    • The types allowed as non-type template parameters.

First things first, how would look a specialization by explicitly defining the type of V for char, for example? It would look like that:

template<char V, template<char> class C>
struct Extract<C<V>> { using Result = NonTypeParam<char, V>; };

, that's a little bit annoying, but as there is a finite number of possibilities. One might find a way to reduce the declarations later on. Let's add another specialization, a victim template, and put that to the test...

template<typename T, T V>
struct NonTypeParam { using Type = T; static constexpr T Value = V; };

template<typename T> struct Extract { using Result = void; };

template<char V, template<char> class C>
struct Extract<C<V>> { using Result = NonTypeParam<char, V>; };

template<std::size_t V, template<std::size_t> class C>
struct Extract<C<V>> { using Result = NonTypeParam<std::size_t, V>; };

template<std::size_t I> struct TestNonType1 {};

using Result          = typename Extract<TestNonType1<42>>::Result;
using RType           = typename Result::Type; // std::size_t
constexpr auto rValue = Result::Value;         // 42

No surprises, it works as expected...
... What are the possible types now?
According to the standard on template parameters:

A non-type template parameter must have a structural type, which is one of the following types (optionally cv-qualified, the qualifiers are ignored):

  • lvalue reference type (to object or to function);
  • an integral type;
  • a pointer type (to object or to function);
  • a pointer to member type (to member object or to member function);
  • an enumeration type;
  • std::nullptr_t; (since C++11)

For our case, the question asked for integral types.
Well, what does the standard says about integral types.
Let's have a look at std::is_integral to find out:

..., if T is the type bool, char, char8_t (since C++20), char16_t, char32_t, wchar_t, short, int, long, long long, or any implementation-defined extended integer types, including any signed, unsigned, and cv-qualified variants.

Ouch!

As there is 9 types – if one exclude char8_t (only from C++20) and consider that implementation-defined integer types are most of the time aliases of these integral type – one would have to do specializations for:

  • 9 signed.
  • 9 signed const.
  • 9 signed volatile.
  • 9 signed const volatile.
  • Which does 36 specializations.
  • And then, add 36 more for the unsigned versions?!

Disclaimer [ Notice ]

  • Without a doubt, this is the reason why no one (maybe really no one else) did that before...

Wait, wait, wait a minute...

One should think about that a second time and, once again, ask the right questions:

  • How is a non-type parameter 'read'/'interpreted' ?
  • Does it make any sense for it to be volatile ?
  • If its value is part of the typename, isn't const implied somehow ?

You certainly found the answers by yourself...

– Likewise, there is no unsigned version of char16_t, char32_t and wchar_t.
– Moreover, if one reads a second time a little more carefully what the standard says about template parameters, one might see something that haven't had the attention it deserves...

A non-type template parameter must have a structural type, which is one of the following types (optionally cv-qualified, the qualifiers are ignored)

Well, well, well...

– This will do a lot more job than one would have excepted at first... :P
– It turn out that, in the end, only 14 specializations of the Extract template are enough to manage 99% of all possible integral types...

... I think that's way too much writings for such a small amount of code.

Please find the solutions here below, – let here for the posterity – in hope it might be useful to someone (at least for the interesting 'trickery' used in the second example).

° Personal Comment

It is hard to me to believe that this 9 years old question haven't find an answer earlier (as well as thinking that I would be the only "dumb" guy who found this answer)


Working Solutions

Solution #1

Nothing special here. It's just a regular specialization of a template...

template<typename T, T V>
struct NonTypeParam { using Type = T; static constexpr T Value = V; };

namespace Details1 {

template<typename T> struct Extract { using Result = void; };

template<typename T, T V> using R = NonTypeParam<T, V>;

// boolean
template<bool V, template<bool> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
// signed types
template<char      V, template<char>      class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<char16_t  V, template<char16_t>  class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<char32_t  V, template<char32_t>  class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<wchar_t   V, template<wchar_t>   class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<short     V, template<short>     class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<int       V, template<int>       class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<long      V, template<long>      class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<long long V, template<long long> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
// unsigned types
template<unsigned char      V, template<unsigned char>      class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<unsigned short     V, template<unsigned short>     class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<unsigned int       V, template<unsigned int>       class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<unsigned long      V, template<unsigned long>      class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<unsigned long long V, template<unsigned long long> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };

} /* namespace Details1 */

template<typename T>
struct Extract1
{
    using Result = typename Details1::Extract<T>::Result;
};

// Victim template:
template<std::size_t I> struct TestNonType1 {};

// Usage:
using          Param  = typename Extract1<TestNonType1<42>>::Result;
using          PType  = typename Param::Type; // std::size_t
constexpr auto pValue = Param::Value;         // 42

Solution #2

In this solution, one leverages the power of decltype to declare function template overloads, which will never be defined anywhere...

template<typename T, T V>
struct NonTypeParam { using Type = T; static constexpr T Value = V; };

namespace Details2 {

template<typename T, T V> using R = NonTypeParam<T, V>;

// boolean
template<bool V, template<bool> class C> R<decltype(V), V> Extract(C<V> && _);
// signed types
template<char      V, template<char>      class C> R<decltype(V), V> Extract(C<V> && _);
template<char16_t  V, template<char16_t>  class C> R<decltype(V), V> Extract(C<V> && _);
template<char32_t  V, template<char32_t>  class C> R<decltype(V), V> Extract(C<V> && _);
template<wchar_t   V, template<wchar_t>   class C> R<decltype(V), V> Extract(C<V> && _);
template<short     V, template<short>     class C> R<decltype(V), V> Extract(C<V> && _);
template<int       V, template<int>       class C> R<decltype(V), V> Extract(C<V> && _);
template<long      V, template<long>      class C> R<decltype(V), V> Extract(C<V> && _);
template<long long V, template<long long> class C> R<decltype(V), V> Extract(C<V> && _);
// unsigned types
template<unsigned char      V, template<unsigned char>      class C> R<decltype(V), V> Extract(C<V> && _);
template<unsigned short     V, template<unsigned short>     class C> R<decltype(V), V> Extract(C<V> && _);
template<unsigned int       V, template<unsigned int>       class C> R<decltype(V), V> Extract(C<V> && _);
template<unsigned long      V, template<unsigned long>      class C> R<decltype(V), V> Extract(C<V> && _);
template<unsigned long long V, template<unsigned long long> class C> R<decltype(V), V> Extract(C<V> && _);

} /* namespace Details2 */

template<typename T>
struct Extract2
{
    using Result = decltype(Details2::Extract(std::declval<T>()));
};

// Victim template:
template<unsigned long long I> struct TestNonType2 {};

// Usage:
using          Param  = typename Extract2<TestNonType2<42>>::Result;
using          PType  = typename Param::Type; // std::size_t
constexpr auto pValue = Param::Value;         // 42

° Update (July 25, 2021)

  • Here below is an example of how a template declared with any type of non-type parameter can be decomposed.
  • Unfortunately, though this little piece of code seems to be using only C++11 language features, it cannot be compiled as C++11.
  • This code works perfectly, and does what it is meant to do, but is has to be compiled as C++17.
  • There definitely had a change in the standard since the addition of auto as a non-type template parameter which, I think (but couldn't find info on it), make the compilers interpret the pattern <typename T, template <T> class C, T V> as if it was a synonym of <auto V>.
/* Template allowing to separately retrieve the components
 * of a template having one non-type parameter.
 */
template<typename T, template <T> class C, T V>
struct TmplInfo;

/* Function to decompose a template having one non-type
 * parameter and return its corresponding TmplInfo type.
 */
template<typename T, template <T> class C, T V>
inline constexpr TmplInfo<T, C, V> ToTmplInfo(C<V> && o);

/* Our victim template...
 */
template<std::size_t I> struct Victim;

/* Aliases Victim<42> and then decompose it to a TmplInfo.
 */
using V42   = Victim<42>;
using VInfo = decltype(ToTmplInfo(std::declval<V42>()));

/* Compiled for x64 arch, this gives:
 * using VInfo = TmplInfo<std::size_t, Victim, 42Ui64>;
 */
like image 190
Tenphase Avatar answered Sep 28 '22 09:09

Tenphase


This other question is asking basically the same thing but for template type parameters, rather than template non-type parameters: template metaprogramming: (trait for?) dissecting a specified template into types T<T2,T3 N,T4, ...>

For type parameters, it really is easy. The code looks like this:

#include <tuple>
#include <vector>

template <class T> struct explode;

template <template <class... Args> class T, class... N>
struct explode<T<N...>>
{
    typedef T<N...> type;
    template <class... Args> using template_ = T<Args...>;
    template <int I> using type_parameter =
        typename std::tuple_element<I, std::tuple<N...>>::type;
};

#if TESTING
void test_harness()
{
    typedef explode<std::vector<int>> exv;

    exv::template_<char> vchar;  // The second parameter still has its default argument!
    exv::template_<exv::type_parameter<0>, exv::type_parameter<1>> vint;

    static_assert(std::is_same<exv::template_<char>, std::vector<char>>::value, "");
    static_assert(std::is_same<decltype(vchar), std::vector<char>>::value, "");
    static_assert(std::is_same<decltype(vint), std::vector<int>>::value, "");
    static_assert(std::is_same<exv::type, std::vector<int>>::value, "");
    static_assert(std::is_same<exv::type_parameter<0>, int>::value, "");
    static_assert(std::is_same<exv::type_parameter<1>, std::allocator<int>>::value, "");
}
#endif

But for non-type parameters, I haven't figured out if it's possible yet. You can start with the similar-looking code

template <class... ArgTypes, template <ArgTypes... Args> class T, ArgTypes... N>
struct explode<T<N...>>
{
    typedef T<N...> type;
    template <ArgTypes... Args> using template_ = T<Args...>;
    template <int I> using type_of_parameter =
        typename std::tuple_element<I, std::tuple<ArgTypes...>>::type;
    template <int I> struct nontype_parameter {
        static constexpr type_of_parameter<I> value() {
            return std::get<I>(std::tuple<ArgTypes...>(N...));
        }
    };
};

};

but Clang (at least) doesn't accept it:

test.cc:8:8: warning: class template partial specialization contains a template
      parameter that can not be deduced; this partial specialization will never
      be used
struct explode<T<N...>>
       ^~~~~~~~~~~~~~~~
test.cc:7:20: note: non-deducible template parameter 'ArgTypes'
template <class... ArgTypes, template <ArgTypes... Args> class T, ArgTypes... N>
                   ^

and even if you made that problem go away somehow, you'd still have to replace std::get with a hand-coded constexpr version, because the standard library's std::get isn't constexpr for whatever reason.

like image 26
Quuxplusone Avatar answered Sep 28 '22 09:09

Quuxplusone