Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++17 alias template with template class default arguments

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.)

like image 924
Mark Avatar asked May 02 '18 13:05

Mark


People also ask

Can default argument be used with the template class?

Can default arguments be used with the template class? Explanation: The template class can use default arguments.

Can template parameters have default values?

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.

Can we pass Nontype parameters to templates?

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.

What are template arguments enlist types of template arguments?

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.)


3 Answers

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.

like image 164
Barry Avatar answered Oct 29 '22 18:10

Barry


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.

like image 23
Jodocus Avatar answered Oct 29 '22 16:10

Jodocus


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:

  • You don't need C++17
  • You don't have to re-specify the defaults (DRY principle)
  • You don't need any extra classes, functions, etc.
  • It can be combined with a workaround for alias template specialization

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;
}
like image 35
Mark Avatar answered Oct 29 '22 16:10

Mark