0

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.

Tyg13
  • 321
  • 2
  • 10
  • Different question, same answer I believe. https://stackoverflow.com/questions/34940875 – Drew Dormann Aug 02 '18 at 22:37
  • 1
    Make sure you try weird code in clang - it often has better error messages and is a better conforming compiler https://godbolt.org/g/kdRQqE: :20:30: error: template parameter pack must be the last template parameter typename Tag, typename ... Tags, – xaxxon Aug 02 '18 at 22:39
  • 1
    Possible duplicate of [Parameter pack must be at the end of the parameter list... When and why?](https://stackoverflow.com/questions/34940875/parameter-pack-must-be-at-the-end-of-the-parameter-list-when-and-why) – xaxxon Aug 02 '18 at 22:40
  • So the fact that the last template argument is default doesn't affect that the parameter pack needs to last. Darn. Perhaps there's some way to move the SFINAE stuff into the variable template? I imagine this is where I'd have to switch to a static function, instead. – Tyg13 Aug 02 '18 at 22:53
  • @Tyg13 how would it know if you were specifying the last parameter or if you were giving an additional parameter to the pack and then expecting the default parameter to then be added on to the end? if you say `bool char void` do you mean `(bool char) void` or `(bool char void) void`? (the bits in paren's meaning what is eaten by the ppack) – xaxxon Aug 02 '18 at 23:14
  • You _can_ have a defaulted template parameter after a parameter pack. – Barry Aug 03 '18 at 00:01

1 Answers1

1

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.

Barry
  • 286,269
  • 29
  • 621
  • 977