Specialization of template variable (for template template class)

847 views Asked by At

When I try specialize a template variable for a generic container (e.g. std::list<...>, and not for an specific one, e.g. std::list<double>) I get a link error with gcc 5.3 (but not with clang 3.5)

/tmp/ccvxFv3R.s: Assembler messages:
/tmp/ccvxFv3R.s:206: Error: symbol `_ZL9separator' is already defined

http://coliru.stacked-crooked.com/a/38f68c782d385bac

#include<string>
#include<iostream>
#include<list>
#include<forward_list>
#include<vector>

template<typename T> std::string const separator = ", ";
template<typename... Ts> std::string const separator<std::list<Ts...>        > = "<->";
template<typename... Ts> std::string const separator<std::forward_list<Ts...>> = "->";

int main(){

    std::cout << separator<std::vector<double>> << '\n';
    std::cout << separator<std::list<double>> << '\n';
    std::cout << separator<std::forward_list<double>> << '\n';

}

(This compiles well with clang 3.5 and works as expected. Also the variadic template is not what causes the problem, I tried with a non-variadic template).

If this is not a bug in gcc, do you think is there a work around? I tried to use class specialization but it is not possible either:

template<class T>
struct separator{
    static std::string const value;
};
template<class T>
std::string const separator<T>::value = ", ";
template<typename... Ts>
std::string const separator<std::list<Ts...>>::value = "<->";
template<typename... Ts>
std::string const sep<std::forward_list<Ts...>>::value = "->";
1

There are 1 answers

2
alfC On

This seems to be a problem with gcc. Workaround (use class templates), like @T.C. suggested.

template<class T>
struct sep{
    static const std::string value;
};
template<class T>
const std::string sep<T>::value = ", ";

template<typename... Ts>
struct sep<std::list<Ts...>>{
    static const std::string value;
};
template<typename... Ts>
const std::string sep<std::list<Ts...>>::value = "<->";

template<typename... Ts>
struct sep<std::forward_list<Ts...>>{
    static const std::string value;
};
template<typename... Ts>
const std::string sep<std::forward_list<Ts...>>::value = "->";

And later a template variable (so to have the same interface)

template<typename T> std::string const separator = sep<T>::value;

This works in both gcc and clang.


Or also suggested by @T.C., use a static function member instead of a static member (less code)

template<class T>
struct sep{
    static std::string value(){return ", ";}
};
template<typename... Ts>
struct sep<std::list<Ts...>>{
    static std::string value(){return "<->";}
};

template<typename... Ts>
struct sep<std::forward_list<Ts...>>{
    static std::string value(){return "->";}
};
...
template<typename T> std::string const separator = sep<T>::value();

Or use constexpr const char*

template<class T>
struct sep{static constexpr const char* value = ", ";};

template<typename... Ts>
struct sep<std::list<Ts...>>{static constexpr const char* value = "<->";};

template<typename... Ts>
struct sep<std::forward_list<Ts...>>{static constexpr const char* value = "->";};
...
template<typename T> std::string const separator = sep<T>::value;

I tried to use const_str (a constexpr-friendly version of std::string) but I got strange linker errors.