17

Non-type template parameters are obviously ones that aren't types, for example:

template<int x>
void foo() { cout << x; }

There are other options than int in that case, and I'd like to refer to this great answer.

Now, there's one thing that bugs me: structs. Consider:

struct Triple { int x, y, z; };

Triple t { 1, 2, 3 };

template<Triple const& t>
class Foo { };

Now, using normal nontype reference semantics, we can write:

Foo<t> f;

What's worth noting here is that t can't be constexpr or even const, because that implies internal linkage, which basically means that the line won't compile. We can bypass that by declaring t as const extern. That itself might be a bit weird, but the one that really made me wonder was why this isn't possible:

Foo<Triple { 1, 2, 3 }> f;

We get a really decent error from compiler:

error: Triple{1, 2, 3} is not a valid template argument for type const Triple& because it is not an lvalue.

We can't specify Triple in template by value, because that's disallowed. However, I fail to understand the real problem with that small line of code. What's the reasoning behind not allowing using structs as value parameters. If I can use three ints, why not a struct of three ints? If it has only trivial special members, it shouldn't be really different in handling than just three variables.

Community
  • 1
  • 1
Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
  • 2
    The standard only allows int's, and member pointers I think. You can't even have double or float as non-type parameters. Or even const char* which all have been useful (and surprisingly worked on VC6). Not sure if anything changed for C++11 actually judging by some of the answers. – Pete Apr 09 '13 at 08:18
  • 1
    @Pete `const char*` has the problem of inequality between the same literals written in different places, the same stays for double/float. Also, you skipped the references. My question wasn't about if that's possible (obviously not), but rather *why it's not*. – Bartek Banachewicz Apr 09 '13 at 08:21
  • 1
    I'm guessing that there were potentially too many possibilities to get anything honed down for the standard. Its a much easier thing to specify if it is limited to just int's. Perhaps it will be available in the next C++ standard in 50 years time. – Pete Apr 09 '13 at 08:24
  • Why should there be inequality for float/double? – Pete Apr 09 '13 at 08:28
  • 1
    @Pete Do you test two float variables for equality by using `a == b`? – Bartek Banachewicz Apr 09 '13 at 08:30
  • It depends upon the context - if you have a float constant, you'd expect it to be equal to itself and have the exact same binary layout. Comparing floats/doubles for equality with zero for the purposes of preventing divide by zero can be a reasonable thing to do. If it is the result of a mathematical operation e.g. sin() then you'd expect to use a fuzzy comparison. – Pete Apr 09 '13 at 08:34
  • as the error message says, `Triple {1,2,3}` is indeed not an lvalue. did you try `&Triple {1,2,3}`? – P Varga Apr 09 '13 at 09:02
  • 1
    For the same reason there is no implicitly defined `operator==`? – curiousguy Jun 28 '13 at 01:04
  • @curiousguy care to elaborate why it's not implicitly defined? Perhaps some link will do. – Bartek Banachewicz Jun 28 '13 at 08:57
  • @BartekBanachewicz I do not have the link now, the reasonning is that programmers should really say what they mean (memberwise comparison is not always fine) and there is no implicit comparison of structure in C either (but there is copy of structures in C) so there is no C compatibility argument. In practice, more classes need copying than comparison, and memberwise copy is more often correct than memberwise comparison. – curiousguy Jun 28 '13 at 14:14
  • @curiousguy *and memberwise copy is more often correct than memberwise comparison.* I would say exactly otherwise. – Bartek Banachewicz Jun 28 '13 at 14:15
  • @BartekBanachewicz F.ex. for a `fraction` class, memberwise copy is fine, memberwise comparison is not (unless you always normalise). What do you have in mind as a typical counter example? – curiousguy Jun 28 '13 at 14:17
  • @curiousguy anything that manages memory or id. Memberwise copy is nearly always incorrect. – Bartek Banachewicz Jun 28 '13 at 14:25
  • 1
    @BartekBanachewicz 1) You are supposed to use tools like `string` and STL containers to handle memory. 2) For anything containning pointers, if memberwise copy is incorrect (ie if it isn't an iterator), then memberwise compare is wront too! – curiousguy Jun 28 '13 at 15:04

3 Answers3

23

Updated answer for users:

C++20 adds support for class literal (class with constexpr constructor) non-type template parameters, which would allow the example in the original question to work, provided the template parameter is accepted by value:

template<Triple t> // Note: accepts t by value
class Foo { };

// Works with unnamed instantiation of Triple.
Foo<Triple { 1, 2, 3 }> f1 {};

// Also works if provided from a constexpr variable.
constexpr Triple t { 1, 2, 3 };
Foo<t> f2 {};

Further, all template parameter instances of Triple { 1, 2, 3 } throughout the program will refer to the same static storage duration object:

template<Triple t1, Triple t2>
void Func() {
    assert(&t1 == &t2); // Passes.
}

constexpr Triple t { 1, 2, 3 };

int main()
{
    Func<t, Triple {1, 2, 3}>();
}

From cppreference:

An identifier that names a non-type template parameter of class type T denotes a static storage duration object of type const T, called a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template parameter. All such template parameters in the program of the same type with the same value denote the same template parameter object.

Note that there are quite a few restrictions on the class literal types allowable by template parameters. For more detail, checkout this blog post I wrote explaining the usage and restrictions of literal class NTTPs: Literal Classes as Non-type Template Parameters in C++20.

CygnusX1
  • 20,968
  • 5
  • 65
  • 109
Kevin Hartman
  • 371
  • 2
  • 4
14

It would be easy to make just this bit work, but then people would complain about how using struct template parameters does not work in all the same situations the other template parameters do (consider partial specializations, or what to do with operator==).

In my opinion, it's too messy to get the whole cake, and getting just one tiny slice isn't satisfying enough, and possibly more frustrating. Making just this tiny bit work won't give me more power than something like the following, which has the additional advantage of working with all kinds of stuff (including partial specializations) out of the box.

template <int X, int Y, int Z>
struct meta_triple {
    // static value getters
    static constexpr auto x = X;
    static constexpr auto y = Y;
    static constexpr auto z = Z;
    // implicit conversion to Triple 
    constexpr operator Triple() const { return { X, Y, Z }; }
    // function call operator so one can force the conversion to Triple with
    // meta_triple<1,2,3>()()
    constexpr Triple operator()() const { return *this; }
};
Srivishnu
  • 759
  • 1
  • 6
  • 15
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
7

You can define t as const extern, giving it external linkage. Then the construct works:

struct Triple { int x, y, z; };

const extern Triple t { 1, 2, 3 };

template<Triple const& t>
class Foo { };

Foo<t> f;

Live example.

The reason why you can't pass a temporary to a reference template parameter is that the parameter is a reference. You'd get the same error if the template parameter was const int& and you tried to pass 7. Example.

EDIT

The difference between three ints and a struct containing three ints is that all literals of type int are really the same value (all occurences of 7 are just seven), while each constructor call to a structure conceptually creates a new instance. Take this hypothetical example:

template <Triple t>
struct Foo {};

Foo<Triple {1, 2, 3}> f1;
Foo<Triple {1, 2, 3}> f2;

I think it would introduce extra complexity to "match" those two constructor invocations into the same template instantiation.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 3
    Hm, so it boils down to the fact that the language doesn't have a trivial `op==` for POD structs, and thus two different structs remain different in template context, even if they have exactly the same data inside. – Bartek Banachewicz Apr 09 '13 at 08:27