Non-type template parameter with default value of type template parameter

Viewed 1999

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?

3 Answers

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

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;
}

You could make a default value to supply.

template<class T>
constexpr T brace_init_value{};

Then used as:

template<typename T, T DEFAULT = brace_init_value<T> >
class Bar
{

Thanks to @Marek R for his idea.

My solution to the problem is the following code which is compiling with gcc in C++14:

#include <iostream>
#include <string>
#include <type_traits>

template <typename T>
constexpr typename std::enable_if<std::is_class<T>::value, T>::type BarDefaultValue(const int &)
{
    return {};
}

template <typename T>
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type BarDefaultValue(const int &ret)
{
    return static_cast<T>(ret);
}

struct Foo
{
   int i;
   int j;
};

std::ostream& operator<<(std::ostream& out, const Foo& a)
{
    return out << a.i << ',' << a.j;
}

template<typename T, int DEFAULT = 0>
class Bar
{
public:
    Bar(): mVal(BarDefaultValue<T>(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;
  Bar<Foo> bar2;
}

The output of this file is:

Hello 
Bar constructor with mVal = 10
Bar constructor with mVal = 0,0
Related