It seems like C++17 added the ability to drop the "<>" on template classes when all the arguments have defaults (just like we've been able to do with functions for a long time) e.g.:
template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };
int main()
{
MyStruct<2> a;
MyStruct<> b; // old way to use defaults
MyStruct c; // new way to use defaults
return 0;
}
However, it seems like that feature no longer works when using an alias template e.g.:
template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };
template<int LENGTH = 1>
using MyAlias = MyStruct<LENGTH>;
int main()
{
MyAlias<2> a;
MyAlias<> b; // old way still works
MyAlias c; // new way doesn't compile:
// gcc 7.3: missing template arguments before 'c'
// clang 6.0.0: declaration of variable 'c' with deduced type 'MyAlias' requires an initializer
return 0;
}
That seems like unexpected behavior to me. Are there any workarounds to still allow the "<>" to be dropped? (I know a separate typedef could be created with a different name, e.g.: using MyAlias2 = MyStruct<>, but I want the same exact name. I also know a define could trick it, e.g. #define MyAlias MyStruct, but assume that would only be a last resort.)
Can default arguments be used with the template class? Explanation: The template class can use default arguments.
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.
Template non-type arguments in C++It is also possible to use non-type arguments (basic/derived data types) i.e., in addition to the type argument T, it can also use other arguments such as strings, function names, constant expressions, and built-in data types.
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
That seems like unexpected behavior to me. Are there any workarounds to still allow the "<>" to be dropped?
It's expected behavior. Well, depends on what you expect I guess. Class template argument deduction only applies in the context of using a primary class template name without any template parameters specified, and only in the context of creating objects.
It does not apply in the context of alias templates (as demonstrated in the OP). And it does not apply in the context of function template deduction.
Unless somebody proposes to change this, the workaround is to just write MyAlias<>
.
There is a proposal to generalize using
declaration, so that hypothetically you could write using MyAlias = MyStruct;
and have that be an alias template. In this context, it would seem reasonable to allow MyAlias c;
since MyAlias
directly names a class template.
But the general problem is more complex, since alias templates could do things like reorder types or add new ones. And you'd have to answer questions like:
template <typename T> using tuple_int = std::tuple<T, int>;
tuple_int t(4, 2); // would this work? how?
tuple_int u(4, '2'); // what about this?
I'm not saying there isn't an answer. I'm just saying that it's not a trivial thing.
Are there any workarounds to still allow the "<>" to be dropped?
A possible workaround may be a transparent inheritance:
template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };
template<int LENGTH = 1>
struct MyAlias : MyStruct<LENGTH> { };
int main()
{
MyAlias<2> a;
MyAlias<> b;
MyAlias c;
return 0;
}
A (possibly) dangerous side-effect is however that the base class has no virtual destructors, leading to a possible memory leak if used polymorphically.
That seems like unexpected behavior to me.
Class template argument deduction, which enables the feature you are trying to use, appears to require a name of a real class template, not that of an template alias. What the compiler basically does is something like transforming
MyStruct obj;
to
template <int LENGTH=1>
MyStruct<LENGTH> f() { return MyStruct<Length>{ }; }
auto obj = f();
However, for aliases, you could do something like this:
template <int LENGTH = 1>
using MyAlias = MyStruct<LENGTH + 1>;
The above transformation totally would miss that "+ 1" if it just replaced the name MyAlias
by MyStruct
, implying there is no trivial solution to this problem - but by this time nowhere in the standard this case is handled, so its understandable it will not compile.
I don't recommend this approach, but I found a way that will work even if you don't have C++17. You could use a macro (like mentioned in the question) if you just want simple text substitution and you have C++17. Otherwise, you can do it this way if you don't mind the slightly different syntax:
template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };
#define MyAlias(...) MyStruct<__VA_ARGS__>
using MyAlias = MyStruct<>;
int main()
{
MyAlias(2) a; // MyAlias<2>
MyAlias() b; // MyAlias<>
MyAlias c;
}
There are a couple advantages over the other approaches:
As an example, pretend you wanted to make a class to emulate floating point numbers of various sizes. If the size matches a built-in type, you want to use that directly for efficiency, otherwise you use your class. Also, you want the size to default to the most efficient size on your platform to reduce verbosity.
Ideally, I would like the be able to use alias templates likes:
template<int BITS>
struct BasicReal final
{
constexpr BasicReal(const double){};
};
template<int BITS = 64>
using Real = BasicReal<BITS>;
template<> // Alias template specialization if it worked
using Real<64> = double;
template<> // Alias template specialization if it worked
using Real<32> = float;
int main()
{
Real r = 1.2; // Alias template argument deduction if it worked
Real<16> r16 = 1.2;
Real<32> r32 = 1.2;
Real<64> r64 = 1.2;
return r;
}
However, for now, I can use the following workaround (even in C++11):
template<int BITS>
struct BasicReal final
{
constexpr BasicReal(const double){};
};
template<int BITS = 64>
struct RealAlias
{
using Type = BasicReal<BITS>;
};
template<>
struct RealAlias<64>
{
using Type = double;
};
template<>
struct RealAlias<32>
{
using Type = float;
};
#define Real(...) RealAlias<__VA_ARGS__>::Type
using Real = RealAlias<>::Type;
int main()
{
Real r = 1.2;
Real(16) r16 = 1.2;
Real(32) r32 = 1.2;
Real(64) r64 = 1.2;
return r;
}
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