I've been trying out the new(ish) C++14 variable template features, and ran into this curious error during compilation (g++ 6.3.0, but also tested using 8.1.0)
templated_variables.cpp:32:19: error: wrong number of template arguments (1, should be at least 1)
const std::string config_data<WantName> = "Name: grep";
There are more errors than that, but they're all of that same kind. The code is as follows
#include <type_traits>
#include <string>
#include <iostream>
struct Want {};
struct WantName : Want {};
struct WantDir : Want {};
template<bool... values>
struct all_of : std::true_type {};
template<bool... values>
struct all_of<true, values...> : all_of<values...> {};
template<bool... values>
struct all_of<false, values...> : std::false_type {};
template <
typename Tag, typename ... Tags,
typename =
typename std::enable_if<
all_of<
std::is_base_of<Want, Tag>::value,
std::is_base_of<Want, Tags>::value...
>::value
>::type
>
const std::string config_data = config_data<Tag> + '\n' + config_data<Tags...>;
template <>
const std::string config_data<WantName> = "Name: grep";
template <>
const std::string config_data<WantDir> = "Directory: /usr/bin/";
int main() {
std::cout << config_data<WantDir, WantName> << '\n';
std::cout << config_data<WantDir> << '\n';
std::cout << config_data<WantName> << '\n';
}
It seems like the issue here is the SFINAE-style std::enable_if
, since if I remove that, this compiles with no issue. But curiously, if I remove every instance of config_data<Want*>
with config_data<Want*, Want>
(or some other class that has Want
as a base, we get no compile error either.
My question is, how can I avoid having to either
(a) Lose the ability to prevent users of this template from passing in random types as template parameters, or
(b) Require the use of an unnecessary base parameter in every instantiation of the variable template.
I realize that in this (contrived) example, (a) is not a legitimate concern. Any instantiation of the template with a type that doesn't implement one of the specializations will fail to compile. But it certainly could be an issue in the general case, and it still doesn't explain why instantiating the template with a valid first parameter, an empty parameter pack, and a blank default parameter leads to a compilation error.
Let's drop the actual default template argument for a second, and give that last parameter a name.
Your primary variable template looks like this:
template <typename Tag, typename... Tags, typename _Unnamed>
const std::string config_data = ...;
And your first specialization is:
template <>
const std::string config_data<WantName> = ...;
So in this specialization, Tag=WantName
, Tags={}
, and _Unnamed
is... what exactly? It's not specified. Indeed, there's no way to actually specify it. It's fine to have a trailing defaulted template parameter, it's just always going to be defaulted. But once you try to specialize it, it's impossible to do.
In C++20, you'll be able to properly constrain this:
template <DerivedFrom<Want> Tag, DerivedFrom<Want>... Tags>
const std::string config_data = ...;
Until then, do you really need SFINAE? Not entirely sure what you get out of it. If you just drop it, everything works fine.