55

Is it possible in "modern C++" (C++17 or greater) to pass a string literal as a parameter to a C++ template?

I realize you could do this with constructor argument; I just thought it would be more convenient to have it as a template argument, rather than buried deep in the cpp file. I was curious if maybe this was a new feature of modern C++. See Pseudo code below of what I'm trying to do:

Pseudo-code Example:

// Header File /////////////////////////
template<constexpr string Name>
class ModuleBase {
public:
    ModuleBase();
    string name;
};

class xyz : ModuleBase<"xyz"> {
public:
    xyz();
};

// Cpp File //////////////////////////
template<string_literal Name>
ModuleBase<Name>::ModuleBase() {
    name = Name;
}

xyz::xyz() : ModuleBase() {

}
AMA
  • 4,114
  • 18
  • 32
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • 2
    FWIW, You cannot put a template class's definitions in a cpp file: https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file – NathanOliver Jul 05 '18 at 15:48
  • 5
    @NathanOliver you can, with lengthy caveats – Caleth Jul 05 '18 at 15:50
  • @Nathan That answer doesn't exactly say that - you certainly _can_ put a template definition in a C++ source file, though it mostly doesn;'t do what you want. –  Jul 05 '18 at 15:50
  • 2
    Yeah, I know, but it is a lot easier to just let the OP read the actual explanation then try and summarize it all in a comment.. – NathanOliver Jul 05 '18 at 15:51
  • You can do it... it just has a weird syntax... template MyClass::Method(T x) { ... }... However, I haven't had any luck getting constructors to accept this syntax using visual studio 2015... but a general method it works fine... – Bimo Jul 05 '18 at 15:51
  • I see where somebody proposed it recently: www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0424r2.pdf – Bimo Jul 05 '18 at 15:53
  • 6
    @BillMoore The problem isn't the syntax, the problem is the semantics - the template _definition_ (not just the declaration!) needs to be visible at usage side - so you'd basically have to either use it like a header anyway (or else only use it locally). – Cubic Jul 05 '18 at 15:54
  • 1
    https://stackoverflow.com/questions/1826464/c-style-strings-as-template-arguments – n. m. could be an AI Jul 05 '18 at 15:59
  • @BillMoore, According to [this trip report](https://botondballo.wordpress.com/2018/03/28/trip-report-c-standards-meeting-in-jacksonville-march-2018/), that paper was accepted by EWG previously, but ultimately pulled, leaving the change in the the below answer the only solution. – chris Jul 05 '18 at 16:11
  • 1
    Am I guessing right that you don't like doing it the old way, declaring the template as `ModuleBase`, having an `extern char const *xyz="xyz"`and then specializing with `ModuleBase` ? – Spencer Jul 05 '18 at 17:24
  • I am not sure if you can do that, but maybe you can get away with first making a compile-time hash of the string literal and then using that hash as a template parameter. You can use this little piece of code I wrote for compile-time hashing of string literals: https://github.com/WojciechMigda/static-string-hash Then you can write: `ModuleBase<"xyz"_hash>` – Wojciech Migda Jul 07 '18 at 22:48
  • Ive seen that done in c++11 using char[]. Can’t remember well. That’s why it’s just a comment. – Regis Portalez Jul 09 '18 at 15:56

4 Answers4

59

Yes, in .

The problem was that determining uniqueness of a template non-type argument was difficult.

adds in a <=> spaceship operator comparison. If it is non-user provided (and based only off non-user provided <=> in turn, repeat recursively) (and a few other requirements; see p0732), the type can be used as a non-type template argument.

Such types can be constructed from raw "strings" in constexpr constructors, including using deduction guides to make them auto-size themselves.

As the size of the data stored is probably going to be part of the type, you'll want to take the type as an auto typed non-type parameter or otherwise auto-deduced type.


Note that placing the implementation of your template in a cpp file is usually a bad idea. But that is another question.

Acorn
  • 24,970
  • 5
  • 40
  • 69
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    This was accepted in EWG, but AFAIK, it still has to go through CWG before it's actually in C++20 (with usual caveats of C++20 not being done until it's done). It really is looking likely, however. – chris Jul 05 '18 at 16:09
  • 20
    I am unreasonably happy that `<=>` is widely known as "the spaceship operator". – Ti Strga Jul 05 '18 at 19:25
  • 1
    @TiStrga It was named that even in the introductory paper: http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0515r0.pdf pew pew – Yakk - Adam Nevraumont Jul 05 '18 at 19:40
  • 2
    @chris You're out of date, it was approved by CWG and plenary voted it in :) – Rakete1111 Jul 05 '18 at 22:37
  • @Rakete1111, My mistake. I skimmed over that section too quickly when looking for confirmation. Semi-interesting that this is listed twice in that trip report. – chris Jul 06 '18 at 07:01
  • Actually, the spaceship operator doesn't have to be `constexpr` to use the class as non-type parameter :) – Rakete1111 Jul 06 '18 at 08:55
  • 1
    @Rakete1111 No? "have a non-user-provided operator<=> returning a type that is implicitly convertible to std::strong_equality , and contain no references." -- the requirement is stronger than `constexpr`, it is non-user provided recursively (which is `constexpr`) – Yakk - Adam Nevraumont Jul 06 '18 at 13:14
  • @Yakk Yes it's stronger but it doesn't mean that its implicitly or is required to be constexpr AFAIK. – Rakete1111 Jul 06 '18 at 17:35
2

Until you get and if you have , you may find the following macro usefull:

#define C_STR(str_) boost::mpl::c_str< BOOST_METAPARSE_STRING(str_) >::value

Then use as follows:

template<const char* str>
structe testit{
};
testit<C_STR("hello")> ti;
darune
  • 10,480
  • 2
  • 24
  • 62
1

It's uglier than directly passing a string literal, but in C++20 it's also possible to get away with using a NTTP constexpr lambda returning the string:

#include <string_view>

template<auto getStrLambda>
struct MyType {
    static constexpr std::string_view myString{getStrLambda()};
};

int main() {
    using TypeWithString = MyType<[]{return "Hello world!";}>;
    return 0;
}

Compiler explorer example here.

James Mart
  • 560
  • 3
  • 16
0

It can be done in C++17 but you need static constexpr storage for the C string before you pass it as name: https://godbolt.org/z/ojnv8xPca

#include<cstddef>
#include<string> //std::size()

namespace util {
    template<std::size_t N>
    using c_str = char const [N];

    template<auto const & str, std::size_t iter = std::size(str), char const ... chars>
    struct c_str_to_arg_pack {
        using arg_pack_t = 
            typename c_str_to_arg_pack<str, iter-1, str[iter-1], chars ... >
                ::arg_pack_t;
    };

    template<auto const & str, char const ... chars>
    struct c_str_to_arg_pack<str, 0, chars ...> {
        using arg_pack_t = c_str_to_arg_pack<str, 0, chars...>;

        template <template <char ...> typename T>
        using apply = T<chars ...>;
    };
    template<auto const & str, char const ... chars>
    using c_str_to_arg_pack_t = typename c_str_to_arg_pack<str, chars ... >::arg_pack_t;

}//end util

#include<string_view>
#include<array>

template <char ... chars>
struct module_base {
    std::string const & get_name() {
        static std::string name = []()
            {
                std::string name;
                name.reserve(sizeof...(chars));
                (name += ... += chars); 
                return name;
            }();
        return name;
    };
    static constexpr std::array<char, sizeof ... (chars)> name_backing {chars ...};
    static constexpr std::string_view constexpr_name {name_backing.begin(), name_backing.size() };
    static int instance_count;
    module_base (){
        ++instance_count;
    }
    virtual ~module_base() = default;
};

template <char ... chars>
int module_base<chars ...>::instance_count {};

template <auto const & name>
struct xyz : util::c_str_to_arg_pack_t<name>::template apply<module_base> //edit, formerly missing 'template'
{};

template <auto const & name>
struct abc : util::c_str_to_arg_pack_t<name>::template apply<module_base> //edit, formerly missing 'template'
{};

#include<iostream>

int main(){
    static constexpr util::c_str<std::size("beans\n")> beans {"beans\n"};
    xyz<beans> test{};
    std::cout << test.get_name();
    
    static constexpr util::c_str<std::size("beans\n")> beans_two {"beans\n"};
    abc<beans_two> test_two{};
    std::cout << test_two.constexpr_name;
    
    std::cout << decltype(test)::instance_count << '\n';
    std::cout << decltype(test_two)::instance_count << '\n';
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83