Class Type Non-Type Template Parameter Initialization Does Not Compile

Class type non-type template parameter initialization does not compile

This template-argument

{.a=1, .b=2}

is not allowed according to the grammar for a template-argument which only allows the following constructs:

template-argument:

constant-expression

type-id

id-expression

A brace-init list is not any of the above constructs, it's actually an initializer and so it cannot be used as a template-argument.

You can be explicit about the type of the object that you use as the template-argument:

Bar<Foo{.a=1, .b=2}> bar;

and this will work, since this is a constant-expression.

Can a braced initializer be used for non-type template argument in C++?

I'd say GCC is wrong.

The grammar for template-argument in [temp.names] says that a template argument must either be a constant-expression, a type-id or an id-expression.

{} is neither an expression, nor a type, nor an (un)qualified name.

a non-type template parameter cannot have type

Clang just doesn't implement class types as non-type template parameters yet, see P1907 in this table.

gcc does implement them but there's actually an issue here. The grammar for template-argument doesn't actually allow for a braced-init-list. This is a clear language defect (there was never a reason to have such a thing before P1907 but now there's certainly no reason to not have it). This is a language bug at the moment. Nevertheless, gcc went ahead and does support a braced-init-list as a template argument... just not a designated-initializer-list.

So that blog post of mine is running ahead of the actual language by a bit... Until the language catches up, even though this is technically unsupported:

takes_tmpl_point<{.x=1, .y=2}>(); 

This is definitely valid:

takes_tmpl_point<Point{.x=1, .y=2}>();

Will it be possible to omit the type name when initializing C++2a class non-type template arguments?

will C++2a allow the concrete type name S to be omitted?

No.

[temp.arg.nontype]/2

A template-argument for a non-type template-parameter shall be a converted constant expression ([expr.const]) of the type of the template-parameter.

In T<{0}>, {0} is not an S: it is not an expression of the type of the template-parameter (S). {0} would be an initializer list (in a context where it would be allowed).


Bonus:

[dcl.init.list]/4

List-initialization can occur in direct-initialization or copy-initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization.

No initialization occur for template-arguments (unless when it does, see [temp.arg.nontype]/1).

template non-type template parameters

Your base declaration does not match your specialization.

The base implementation has template <class...Args> while the specialzation wants template <int eventType, class...Args>.

You also put an extra int that does not belong there in the declaration for the specialization here:

template<int eventType,class...Args>
class Event<int eventType, bool(Args...)> : public IEvent
^^^ here

The adjusted code would look like this

#include <stdio.h>
#include <iostream>
#include <functional>
#include <vector>
using namespace std;

class IEvent
{

public:
int m_EventType;
virtual ~IEvent() {}
};

template<int eventType, class...Args>
class Event : public IEvent {};

template<int eventType,class...Args>
class Event<eventType, bool(Args...)> : public IEvent
{
public:
Event(bool(*func)(Args...)) :m_FnPtr(func)
{
m_EventType = eventType;
m_ListenersList.push_back(m_FnPtr);
}

template <typename T>
Event(T* obj, bool(T::* Func)(Args...))
{
m_EventType = eventType;
m_FnPtr = [obj, Func](Args&&... args)->bool {
return (obj->*Func)(std::forward<Args>(args)...);
};

m_ListenersList.push_back(m_FnPtr);
}

void NotifyListeners(Args&&...args) const
{
for (auto& itr : m_ListenersList)
{
(itr)(std::forward<Args>(args)...);
}

}
private:
std::function<bool(Args...)> m_FnPtr;
std::vector<std::function<bool(Args...)>> m_ListenersList;
};

class Window
{
public:
bool OnKeyUp(bool, double)
{
cout << endl << "Member Function called";
return true;
}

bool OnClicked()
{
cout << endl << "OnClicked";
return true;
}

//using KeyupListenerType = Event<"KeyUp", bool(bool, double)>;
};

int main()
{
Window w;
Event<90,bool(bool, double)> evnt(&w, &Window::OnKeyUp);
//Event<100,bool()> evnt(&w, &Window::OnClicked);
evnt.NotifyListeners(true, 6.8);
return 0;
}

What does the C++20 standard say about usage of subojects as template non-type arguments?

The wording changed as part of P1907R1, which was adopted as part of C++20. Note that the first draft you cited - N4835 - predates this adoption (that draft was published Oct 2019, and this paper was adopted the following month at the Belfast meeting in Nov 2019). The closest draft to C++20 is N4861, which you can also conveniently view in html form.

As a result, the following:

template<int* p> class X {};
int a[10];
struct S
{
int m;
static int s;
} s;

X<&a[2]> x3;
X<&s.m> x4;

is a valid C++20 program, since neither a[2] nor s.m are subobjects of any of: a temporary, a string literal, the result of a typeid expression, or __func__.


The cppreference example has already been updated to reflect this, where the comments now read:

X<&a[2]> x3;  // error (until C++20): address of array element
X<&s.m> x4; // error (until C++20): address of non-static member

Can I use a type parameter in a template for a non-type parameter?

If you are OK to have a fixed default per type you can create a type to define it as a constant and specialise it as needed.

template <typename T> struct MyTypeDefault { static const T value; };
template <typename T> const T MyTypeDefault<T>::value = T();
template <> struct MyTypeDefault<double> { static const double value; };
const double MyTypeDefault<double>::value = 1.0;

template <typename T>
class MyType {
public:
MyType() { value = MyTypeDefault<T>::value; }
private:
T value;
};


Related Topics



Leave a reply



Submit