Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicitly use defaults for some parameters in class template instantiation

A class template can have multiple parameters that all have defaults.

template<typename UnderlyingT0 = int, typename UnderlyingtT1 = long, typename StringT = std::string>
struct options;

Instatiating the template with just default parameters is easy:

options<> my_default_options;

But what if I want to change a subset of parameters?

options<int, int, std::wstring> wstring_options;

It is not obvious that int is a default for the first parameter while for the second it isn't. Is there something like

options<default, int, std::wstring> wstring_options;

in C++?

like image 915
iFreilicht Avatar asked Apr 17 '15 08:04

iFreilicht


People also ask

Can we specify default value for template arguments?

You cannot give default arguments to the same template parameters in different declarations in the same scope. The compiler will not allow the following example: template<class T = char> class X; template<class T = char> class X { };

What is the instantiation of the class template?

The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation.

How do you pass a parameter to a template in C++?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

Which is correct example of template parameters?

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.


3 Answers

No, there is nothing in standard C++ which would enable this. One option, noted by @FlorisVelleman in the comments, is to introduce an alias template:

template <class UnderlyingT1, class StringT = std::string>
using options_defT0 = options<int, UnderlyingT1, StringT>;

This has the drawback of having to explicitly duplicate the default argument of UnderlyingT0 in the alias definition, but as least it' duplicated in one place only.

An alternative option is used by many Boost libraries. They introduce a special tag use_default and make that the default value. Something like this:

struct use_default {};

template<typename UnderlyingT0 = use_default, typename UnderlyingtT1 = use_default, typename StringT = use_default>
struct options
{
  using RealUnderlyingT0 = typename std::conditional<
    std::is_same<UnderlyingT0, use_default>::value,
    int,
    UnderlyingT0
  >::type;

  using RealUnderlyingT1 = typename std::conditional<
    std::is_same<UnderlyingT1, use_default>::value,
    long,
    UnderlyingT1
  >::type;

  using RealStringT = typename std::conditional<
    std::is_same<StringT, use_default>::value,
    std::string,
    StringT
  >::type;
};

Here, the downsides are that 1. you cannot tell the default arguments by looking at the template declaration, and 2. options<> and options<int, long, std::string> are different types.

The former can be fixed by good documentation, and the latter can probably be helped by judicious use of conversion functions and base classes.

like image 133
Angew is no longer proud of SO Avatar answered Oct 12 '22 14:10

Angew is no longer proud of SO


There isn't a way to reuse the default parameters directly. You can use Floris's comment as a way to provide shorthands for common uses, but the template alias will still repeat the defaults.

Alternatively, the options struct could be set up to allow switching out parameters:

template <typename UnderlyingT0 = int,
          typename UnderlyingT1 = long,
          typename StringT = std::string>
struct options {
  template <typename NewT0>
  using WithT0 = options<NewT0, UnderylingT1, StringT>;
  template <typename NewT1>
  using WithT1 = options<UnderylingT0, NewT1, StringT>;
  template <typename NewStringT>
  using WithStringT = options<UnderylingT0, UnderylingT1, NewStringT>;
};

And then use it as

options<>::WithT1<int>::WithStringT<std::wstring>
like image 24
Sebastian Redl Avatar answered Oct 12 '22 16:10

Sebastian Redl


If all your template arguments have defaults like in your example, you could create a helper struct to extract them for you.

template <class T, size_t N>
struct default_for_helper;

template <template <typename...> class T, size_t N, typename... Args>
struct default_for_helper<T<Args...>, N>
{
    using type = std::tuple_element_t<N, std::tuple<Args...>>;
};

template <template <typename...> class T, size_t N>
using default_for = typename default_for_helper<T<>, N>::type;

Then use it like this:

options<default_for<options,0>, int, std::string> o;
like image 6
TartanLlama Avatar answered Oct 12 '22 14:10

TartanLlama