I have the following code:
#include <iostream>
#include <string>
#include <type_traits>
struct Foo
{
int i;
int j;
};
template<typename T, T DEFAULT>
class Bar
{
public:
Bar(): mVal(DEFAULT)
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
~Bar(){}
Bar(const T &i) : mVal(i)
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
Bar &operator=(T const &val)
{
mVal = val;
std::cout << "Bar assignment operator with mVal = " << mVal << "\n";
return *this;
}
explicit operator T() const
{
return mVal;
}
private:
T mVal;
};
int main()
{
std::cout << "Hello \n";
Bar<int, 10> bar1;
}
This is working fine in gcc C++14 as long as the first template parameter in Bar
is of an integral type. If I want to do Bar<Foo, {}>
the following error message is printed:
on-type template parameters of class type only available with '-std=c++2a' or '-std=gnu++2a'
I already expected that. Changing template<typename T, T DEFAULT> class Bar
to template<typename T, T DEFAULT = {}> class Bar
leads to the same error.
Also a template specialization template<typename T> class Bar<T, {}>
does not work for the same reason.
I also tried to experiment with std::enable_if_t<std::is_integral<T>::value>
but could not find a solution that would work.
Is there any possible way to just write Bar<Foo>
and not have to write a separate class like template<typename T, T DEFAULT> class BarDefault
and template<typename T> class Bar
for it?
Just like in case of the function arguments, template parameters can have their default values. All template parameters with a default value have to be declared at the end of the template parameter list.
A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type.
Non-type template arguments are normally used to initialize a class or to specify the sizes of class members. For non-type integral arguments, the instance argument matches the corresponding template parameter as long as the instance argument has a value and sign appropriate to the parameter type.
For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.
A non-type template parameter must have a structural type, which is one of the following types (optionally cv-qualified, the qualifiers are ignored): the types of all base classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.
A template type parameter is a placeholder type that is substituted for a type passed in as an argument. However, template type parameters are not the only type of template parameters available. Template classes and functions can make use of another kind of template parameter known as a non-type parameter.
The first template parameter is the type of elements the container stores and the second template parameter is the defaulted allocator a container of the standard template library has. Even the allocator has a default value such as in the case of a std::vector.
Each function template has a single function parameter whose type is a specialization of X with template arguments corresponding to the template parameters from the respective function template where, for each template parameter PP in the template parameter list of the function template, a corresponding template argument AA is formed.
Template parameters and template arguments - cppreference.com
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)
a floating-point type;
a literal class type with the following properties:
all base classes and non-static data members are public and non-mutable and
the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof. (since C++20)
So basically custom structure as template value parameter is available since c++20.
Demo
You can overcome this problem by providing depending template which job is to provide a default value:
https://godbolt.org/z/RFp_xH
#include <iostream>
#include <string>
#include <type_traits>
struct Foo
{
int i = 42;
int j = 4;
};
std::ostream& operator<<(std::ostream& out, const Foo& a)
{
return out << a.i << ',' << a.j;
}
template<typename T>
struct BarDefaultValue
{
constexpr static T value()
{
return T{};
}
};
template<>
struct BarDefaultValue<int>
{
constexpr static int value()
{
return 42;
}
};
template<typename T, typename D = BarDefaultValue<T>>
class Bar
{
public:
Bar(): mVal(D::value())
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
~Bar(){}
Bar(const T &i) : mVal(i)
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
Bar &operator=(T const &val)
{
mVal = val;
std::cout << "Bar assignment operator with mVal = " << mVal << "\n";
return *this;
}
explicit operator T() const
{
return mVal;
}
private:
T mVal;
};
int main()
{
std::cout << "Hello \n";
Bar<int> bar1;
Bar<Foo> bar2;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With