1

I'd like to have a C++ concept that matches a particular template type regardless of one of the template arguments. I can, of course, do this using some other helper declarations to pick apart the template type. But one of the benefits of concepts and requires expressions in particular is that they eliminate many of the needs for helper types. So I'm wondering if it's possible to do this "all-in-one" with a single concept declaration.

Here's a minimal working example:

#include <algorithm>
#include <compare>
#include <iostream>
#include <memory>
#include <string>

template<typename A> using char_string =
  std::basic_string<char, std::char_traits<char>, A>;

using std::string;

template<typename T> struct AltAllocator : std::allocator<T> {};
using AltString = char_string<AltAllocator<char>>;

template<typename T> constexpr bool is_char_string_v = false;
template<typename A> constexpr bool is_char_string_v<char_string<A>> = true;

template<typename T> concept is_char_string = is_char_string_v<T>;

inline bool
operator==(is_char_string auto const &a, is_char_string auto const &b)
{
  return std::equal(a.begin(), a.end(), b.begin(), b.end());
}

int
main()
{
  string s;
  AltString as;
  std::cout << std::boolalpha << (s == as) << std::endl;
}

I'm hoping to be able to define is_char_string without having to introduce is_char_string_v. In this particular case, knowing that strings contain their allocator type, I could of course "cheat" with something like this:

template<typename T> concept is_char_string =
  std::same_as<T, char_string<typename T::allocator_type>>;

Is there a more general way of writing a self-contained concept that matches some particular template instantiated with any template arguments?

cigien
  • 57,834
  • 11
  • 73
  • 112
user3188445
  • 4,062
  • 16
  • 26

1 Answers1

2

I don't know what the broader problem might be, but we can decompose the problem of checking that something is a basic_string<char, char_traits<char>, A> into the problems of: (1) it's a basic_string and (2) its first two types are char and char_traits<char>.

The first problem is the standard is_specialization_of trait:

template <typename T, template <typename...> class Z>
inline constexpr bool is_specialization_of = false;

template <typename... Args, template <typename...> class Z>
inline constexpr bool is_specialization_of<Z<Args...>, Z> = true;

And the second we can use Boost.Mp11 for general type list manipulation. So either:

template <typename T>
concept char_string = is_specialization_of<T, std::basic_string>
                   && std::same_as<mp_first<T>, char>
                   && std::same_as<mp_second<T>, std::char_traits<char>>;

Or check the latter two together:

template <typename T>
concept char_string = is_specialization_of<T, std::basic_string>
                   && std::same_as<
                           mp_take_c<T, 2>
                           std::basic_string<char, std::char_traits<char>
                       >;

It's safe to just take the first two parameters here, since the third one is defaulted. And since the second one is also defaulted, you could instead compare to std::string instead of std::basic_string<char, std::char_traits<char>.


If the intent is really to avoid any dependencies or any additional types, then I suppose you could write this:

template <typename T>
concept char_string = requires (T t) {
    []<typename A>(std::basic_string<char, std::char_traits<char>, A>){}(t);
};

But I'm not sure that's a good idea.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • The question was whether you can do this in a self-contained way, without introducing another type, since there's already a MWE with an auxiliary type. You've not only added a another type (`is_specialization_of`) but also a dependency on Boost, which is worst than the two ways of doing it in the original post. Perhaps the answer to the question is "No", but then it would be more useful simply to say that it can't be done rather than give code that's no better than the MWE... – user3188445 Jun 02 '21 at 17:31
  • @user3188445 I find this substantively better than the two ways of doing it in your original post, simply because these components that you're disdaining (`is_specialization_of` and Boost.Mp11) are broadly applicable to a wide variety of problems. – Barry Jun 02 '21 at 17:50
  • @user3188445 If your question is simply "Can I write the concept char_string with zero dependencies and the introduction of zero additional types?" then the answer is yes, I suppose you can (I added an implementation above) but it's not great? – Barry Jun 02 '21 at 18:00
  • Thank you, putting a lambda in a simple requirement was the trick I needed! – user3188445 Jun 02 '21 at 18:31
  • Sadly, this doesn't seem to work after all. gcc accepts it, but clang does not, and I think clang is right: https://stackoverflow.com/questions/22232164/why-are-lambda-expressions-not-allowed-in-an-unevaluated-operands-but-allowed-in – user3188445 Jun 16 '21 at 20:40
  • Actually, sorry. This should work in C++20. I think clang is broken. – user3188445 Jun 16 '21 at 20:41
  • @user3188445 Yeah, clang doesn't implement lambdas in unevaluated contexts yet. – Barry Jun 22 '21 at 18:43