Janis Norvelis
Janis Norvelis

Reputation: 93

String literal that depends on the template type parameter?

I have a class that can parse strings (dates). I want to be able to parse normal as well as wide strings:

MyClass x;
x.parse("2018-02-27");
x.parse(L"2018-02-27");

Since the code to parse a normal string and a wide string is basically the same, it makes sense to use a template:

template<typename CharT>
void parse(const CharT *str)
{
    // ...
}

Now, for parsing I am going to use the get_time function. It takes the fmt parameter, which is of type const CharT *, and for which I would like to provide a string literal. It must be a normal or wide string literal, depending on the template type parameter:

template<typename CharT>
void parse(const CharT *str)
{
    tm date;
    basic_istringstream<CharT> date_stream{str};
    date_stream >> get_time(&date, ("%Y-%m-%d" or L"%Y-%m-%d", but how to choose??) );
    // ...
}

I am only interested in two template instantiations: char and wchar_t. I tried to use non-type template parameters, but did not manage to get anything that compiles.

What is the most elegant way to implement the function/template?

Upvotes: 3

Views: 864

Answers (3)

Quentin
Quentin

Reputation: 63124

My first attempt to shoehorn if constexpr in there didn't go well, but a variable template looks fine:

template <typename CharT>
constexpr CharT const *timeFmt;

template <>
constexpr auto timeFmt<char> = "%Y-%m-%d";

template <>
constexpr auto timeFmt<wchar_t> = L"%Y-%m-%d";

template <typename CharT>
void parse(const CharT *str)
{
    std::tm date;
    std::basic_istringstream<CharT> date_stream{str};
    date_stream >> std::get_time(&date, timeFmt<CharT>);
    // ...
}

For the record, here is the ugly thing that came out of my first try:

template<typename CharT>
void parse(const CharT *str)
{
    std::tm date;
    std::basic_istringstream<CharT> date_stream{str};
    date_stream >> std::get_time(&date, []{
        if constexpr (std::is_same_v<CharT, wchar_t>)
            return L"%Y-%m-%d";
        else
            return "%Y-%m-%d";
    }());
    // ...
}

Upvotes: 3

Add a traits class:

template <typename CharT>
struct format {
    static const CharT* const v;
};

template<> const char* const format<char>::v="%Y-%m-%d";
template<> const wchar_t* const format<wchar_t>::v=L"%Y-%m-%d";

then use as:

date_stream >> get_time(&date, format<CharT>::v);

If you were feeling ambitious, you could merge the actual duplicated format into a #define (and then use token pasting to glue an L on the front where necessary) - but actually, I think that's more machinery than it's worth.

Upvotes: 3

jfMR
jfMR

Reputation: 24738

Besides variable templates and class templates, a function template will do as well:

template<typename T> const T* get_format_str();

Then, the corresponding specializations for both char and wchar_t:

template<> const char*    get_format_str<char>()    { return "%Y-%m-%d"; }
template<> const wchar_t* get_format_str<wchar_t>() { return L"%Y-%m-%d"; }

Use this function template in your parse() function template as:

date_stream >> get_time(&date, get_format_str<TChar>());

The advantages to this approach are:

  • It only requires C++98 (for variable templates you need C++14).
  • The pointer to the literal string can't be modified (only the copy of that pointer returned by the function can be modified).

Upvotes: 0

Related Questions