C++ detect if a type is a string object

Viewed 973

It is a bit strange. I am trying to write a code which detects whether a type is a string object. The char * or other object types should lead to false not true. The following code gives me:

error: template parameters not deducible in partial specialization:

I do not understand what the message means. Even searched online, I cannot figure out how to fix this:

#include <iostream>
#include <type_traits>
#include <string>

template <typename T>
struct is_string : std::false_type { };

template <typename T>
struct is_string<std::string> : std::true_type<T> { };

class Temp
{
  int a;
};

int main()
{
    // Expect: false
    std::cout << is_string<int>::value << std::endl;
    std::cout << is_string<char *>::value << std::endl;
    std::cout << is_string<Temp>::value << std::endl;

    // Expect: true
    std::cout << is_string<std::string>::value << std::endl;
    std::cout << is_string<const std::string>::value << std::endl;
    std::cout << is_string<std::string&>::value << std::endl;
}

If there is a out of box std tool, that's welcome

4 Answers

The template specialization should look like this:

template <>
struct is_string<std::string> : std::true_type { };

But even if you use it, your template will return false for cv-qualified string or references to it.

I did not use std::is_same because it rejects types such as const string or string & as string.

The right way to do it is std::is_same_v<std::remove_cv_t<std::remove_reference_t<your_type>>, std::string> (or using std::decay_t, as the other answer suggests).

Or, in C++20: std::is_same_v<std::remove_cvref_t<<your_type>, std::string>.

"Typo" in specialization syntax, it should be:

template <>
struct is_string<string> : std::true_type {};

To handle const std::string or std::string&, you might compose with std::decay

template <typename T>
using is_string_like = is_string<std::decay_t<T>>;

or using std::is_same:

template <typename T>
using is_string_like = std::is_same<std::string, std::decay_t<T>>;

When you create a specialization, you can kinda think of the template parameters of the specialization as "variables" for the specialization. You can give as many as you want, but they must be used in the angle brackets that are part of the class.

When you create a full specialization, you do not provide any template parameters - empty angle brackets.

The other answers by HolyBlackCat and Jarod42 are correct, in that they get rid of your compiler error, and provide a working implementation for your vague specifications.

However, both of them suggest doing more than removing cv-qualifiers from the type. I do not recommend doing this, as it goes against how the type traits in the standard library work and it could be quite surprising to users.

Since you actually want that as a result, I would suggest providing a separate metafunction that uses the basic one, and make it clear that it is removing the reference.

static_assert(std::is_null_pointer<std::nullptr_t>::value);
static_assert(std::is_null_pointer<std::nullptr_t const volatile>::value);
static_assert(not std::is_null_pointer<std::nullptr_t &>::value);

static_assert(std::is_unsigned<unsigned long>::value);
static_assert(std::is_unsigned<unsigned long const volatile>::value);
static_assert(not std::is_unsigned<unsigned long &>::value);

Plus, std::string is just one of the possible standard string types. What about a string using a different character type (e.g., std::wstring), or the same character type but a different allocator (e.g., std::pmr::string)?

Those already show how to limit your trait for just std::string. Here is one way to do it for any standard string type.

namespace detail {
template <typename T>
struct is_string
: std::false_type
{};

// Partial specialization - parameters used to qualify the specialization
template <typename CharT, typename TraitsT, typename AllocT>
struct is_string<std::basic_string<CharT, TraitsT, AllocT>>
: std::true_type
{};
}

template <typename T>
using is_basic_string = detail::is_string<std::remove_cv_t<T>>;

static_assert(is_basic_string<std::string>::value);
static_assert(is_basic_string<std::string const volatile>::value);
static_assert(not is_basic_string<std::string &>::value);
static_assert(is_basic_string<std::wstring>::value);
static_assert(is_basic_string<std::pmr::string>::value);

The specialization should not include typename T, so:

template <>
struct is_string<std::string> : std::true_type<T> { };

Since you want const std::string and std::string& and combinations thereof to count as string too, you may find it easier to inherit from integral_constant instead (which is what false_type and true_type does).

Example:

#include <iostream>
#include <string>
#include <type_traits>

template <typename T>
struct is_string : std::integral_constant<bool,
                                         std::is_same_v<std::string, std::decay_t<T>>> {};

// helper
template<typename T>
inline constexpr bool is_string_v = is_string<T>::value;
Related