6

I'd like to make a string literal I can use as a template argument. It throws the compiler into some kind of endless loop. What is the problem and fix?

template <char...> struct slit { };

template <typename ...A>
constexpr auto make_slit(char const* const s, A const ...args)
{
  return *s ? make_slit(s + 1, *s, args...) : slit<args...>();
}

int main()
{
  auto const tmp_(make_slit("slit"));

  return 0;
}

The obligatory error (with clang++ -std=c++1y):

t.cpp:4:16: fatal error: recursive template instantiation exceeded maximum depth of 256
constexpr auto make_slit(char const* const s, A const ...args)
               ^
t.cpp:6:15: note: in instantiation of function template specialization 'make_slit<char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char,
      char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char, char>' requested here
  return *s ? make_slit(s + 1, *s, args...) : slit<args...>();

EDIT: check out this answer, generating compile time slit types is possible.

user1095108
  • 14,119
  • 9
  • 58
  • 116
  • C++ doesn't seem to support this. Check [this question](http://stackoverflow.com/questions/2033110/passing-a-string-literal-as-a-parameter-to-a-c-template-class). – bosnjak Mar 24 '14 at 08:30
  • Of course it supports this, for example, `slit<'a', 'b', 'c'>()` is working fine, I simply need a generating function. – user1095108 Mar 24 '14 at 08:53
  • @user1095108 your issue is though that `*s ?:` is not a compile time construct as far as I'm aware. Since there's nothing stopping you from calling `string s; cin >> s; make_slit(s.c_str());` – PeterT Mar 24 '14 at 09:00
  • There should be a compiler-error in that regard then, not an endless loop. Note that the function is `constexpr`. – user1095108 Mar 24 '14 at 09:02
  • @user1095108 there is with gcc – PeterT Mar 24 '14 at 09:04
  • @user1095108 hm, well maybe it gets instantiated regardless of the truth value of `?:` since it's part of the expression. – PeterT Mar 24 '14 at 09:26
  • Well, why does it do so? Could be a problem in other contexts too. – user1095108 Mar 24 '14 at 09:39
  • @user1095108 Well even if you don't call it, you probably still need to instantiate it which is a problem here since the instantiation is recursive. I'm guessing this is why people like Andrei Alexandrescu are petitioning so hard for static_if – PeterT Mar 24 '14 at 09:41

2 Answers2

3

Putting aside the looping instantiation, you cannot achieve what you want as a function parameter cannot be used as a constant expression, which is required of template arguments. Meaning the following is not allowed either:

template <typename... A>
constexpr auto make_slit(A const... args)
{
  return slit<args...>();
}

// error
make_slit('a');

If you find this surprising, keep in mind that constexpr functions are a feature to allow some functions to also be usable in a constant expression. Yours isn’t in general however:

char c;
std::cin >> c;
// what is the result type?
make_slit(c);

I should note however that during the design of literal string operators it was suggested that a function template form be allowed (much like they are for integer and floating-point literals), which would achieve exactly what you need:

// allowed...
template<char... Cs>
constexpr slit<Cs...> operator"" _slit()
{ return {}; }

// ...but only for
auto constexpr s = 123_slit;
auto constexpr t = 12.3_slit;

// ... and not
auto constexpr u = "abc"_slit;

This missing functionality was brought up most recently in 2013 with Richard Smith’s n3599: Literal operator templates for strings. Unfortunately I don’t know what the current status of the feature is.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
3

You can find a solution to expand a string literal to a parameter pack here

#include <iostream>

// c++14 has it http://en.cppreference.com/w/cpp/utility/integer_sequence
namespace detail {
    template <int... Is> struct seq { };
    template <int N, int... Is> struct gen_seq : gen_seq<N - 1, N - 1, Is...> {  };
    template <int... Is> struct gen_seq<0, Is...> : seq<Is...> { };
}

constexpr size_t operator"" _len ( const char*, size_t len ){ return len; }

template < char... val > struct slit {};

#define EXPAND_STRING( type_name, str ) \
template <int... Is> slit< str[Is]...> make_##type_name( detail::seq<Is...> ); \
using type_name = decltype( make_##type_name( detail::gen_seq<str##_len>{} ) );

using Manual = slit< 'b', 'a', 'z'>; 
EXPAND_STRING( MyFoo, "foo bar baz");
EXPAND_STRING( MyBar, "bla bli blo blu");

inline std::ostream& operator<<( std::ostream& os, slit<> ) { return os; }
template < char first, char... chars >
std::ostream& operator<<( std::ostream& os, slit<first,chars...> ) {
    return os << first << " " << slit<chars...>{};
}

int main() {
    std::cout << Manual{} << "\n";
    std::cout << MyFoo{} << "\n";
    std::cout << MyBar{} << "\n";
}

EDIT : replaced the constexpr strlen with a custom literal, it returns the length directly and remove the dependency with relaxed constexpr function of C++1y.

galop1n
  • 8,573
  • 22
  • 36