ziyiyituxe
ziyiyituxe

Reputation: 197

C++ nested templates structs

So i have problem with code like this:

I have struct like this

template <int N>
struct Inner
{
    enum
    {
        val = 2*N
    };
};

And i want to achive sth like this:

int v = Outer<Inner<4>>::val;
int b = Outer<false>::val;
cout<< v <<endl;
cout<< b <<endl;

My goal is to created "Outer" struct which takes bool or Inner<int N> and set Outer::val to Inner::val or bool So i have created sth like this (not working):

template <bool B>
struct Outer
{
    enum
    {
        val = B
    };
};

template <Inner<int> I>
struct Outer
{
    enum
    {
        val = I::val
    };
};

Whats wrong with this and how to fix that? (I have seen some similar questions, but still can't apply this to my problem)

Upvotes: 2

Views: 1194

Answers (2)

max66
max66

Reputation: 66200

There are some problems in your code.

First of all: you define two different Outer structs

template <bool B>
struct Outer
 { /* ... */ };

template <Inner<int> I>
struct Outer
 { /* ... */ };

And you can't.

If you want, you can declare an Outer struct and two specializations, but you have to decide what type of template argument Outer has to receive.

Because, looking at your desiderata,

int v = Outer<Inner<4>>::val;
int b = Outer<false>::val;

you want pass to it a type in one case (Inner<4>) and a value in the other case. And you can't.

You have to decide if Outer receive a type or a value. Before C++17, if receive a value, you have to decide the type of the value; starting from C++17, you can declare Outer as receiving a value of a generic type (auto as type of the value).

Problem: a value of Inner<int> can't be a template parameter (but see also the Michael Kenzel's answer, that show a possible C++20 solution based on template values arguments).

So the only solution I see (before C++20) is declare Outer as receiving a type

template <typename>
struct Outer;

Then you can define a Outer specialization for Inner types

template <int N>
struct Outer<Inner<N>>
 { enum { val = Inner<N>::val }; }; // or simply enum { val = N };

For bool values, you have to wrap they in a class; I suggest (starting from C++11) the use of the standard class std::integral_constant and the definition of the following Outer specialization

template <bool B>
struct Outer<std::integral_constant<bool, B>>
 { enum { val = B }; };

The use is as follows

int v = Outer<Inner<4>>::val;
int b = Outer<std::integral_constant<bool, false>>::val;

std::cout << v << std::endl;
std::cout << b << std::endl;

You can also use std::false_type defining b

int b = Outer<std::false_type>::val;

and, starting from C++17, also std::bool_constant (a shorthand for std::integral_constant for bool values)

int b = Outer<std::bool_constant<false>>::val;

Upvotes: 2

Michael Kenzel
Michael Kenzel

Reputation: 15943

A template parameter can be either a type, a value (non-type), or a template [temp.param]. What you're trying to achieve would require your template Outer to have a parameter that can be either a type or a value. Unfortunately, this is not possible.

What you could do is wrap your bool value in a type:

template <bool b>
struct InnerBoolean
{
    static constexpr bool val = b;
};

and then have one common definition for Outer

template <typename T>
struct Outer
{
    enum
    {
        value = T::val
    };
};

and then use Outer<Inner<4>> and Outer<InnerBoolean<False>>.

Rather than write your own wrapper, if you rename val to value, you can use the wrappers that the standard library provides in std::bool_constant and std::true_type and std::false_type.

While up to C++17, a non-type template parameter cannot be of class type [temp.param]/4, C++20 will lift this restriction and allow template parameters of any literal type. Thus, as long as Inner can be a literal type, you will be able to just pass a value of type Inner directly and use an auto template parameter:

struct Inner
{
    int N;

    constexpr Inner(int N) : N(N) {}

    constexpr operator int() const { return 2*N; }
};

template <auto val>
struct Outer
{
    enum
    {
        value = val
    };
};

auto a = Outer<Inner(4)>::value;
auto c = Outer<false>::value;

Upvotes: 2

Related Questions