简体   繁体   中英

C++ Concepts for checking if a function exists in a container class and implicit deduction rules

I'm trying to figure out how to do a few things that seem to be tricky to me with regards to concepts and template type like std::vector.

  1. I am trying to apply a compile time constraint similar to how I used std::movable on T, but on C with PushBackMovable. It works with the requires at the tail end of the function decl, but I would like to be consistent and put my constraints within the template args. I tried replacing "class C" with "PushBackMovable C", but that not so epically failed, but is closer to what I would prefer.

  2. I'm trying to use the template types within the templated template parameter.
    2a. Is there any way to just declare C and use the template args within it inside the signature of the function? For example, can I remove "T" and "Alloc", and ride with "_T" and "_Alloc"? It seems that I cannot access those parameters. I want to save some code space.
    2b. Is their a way to remove the empty <> on C on the first arg of the operator?
    2c. The compiler can deduce the template types of C on the requires if I just had PushBackMovable, but then declval barfs. Are their any tricks I"m missing to implicitly determine template parameters, specifically on the "C" instances? It would be nice to just say "C".

  3. Is their an easier way to check for existence of a method than below?

Here is my example code for both cases:

#include <vector>
#include <type_traits>
#include <algorithm>

template<typename T, typename Alloc, template<typename _T=T, typename _Alloc=Alloc> class C >
concept PushBackMovable = std::is_same_v<decltype(std::declval<C<T,Alloc> >().push_back(std::move(T{}))),void> &&
                          std::is_same_v<decltype(std::declval<C<T,Alloc> >().push_back(T{})),void>; 

template<std::movable T, typename Alloc, template<typename _T=T, typename _Alloc=Alloc> class C > 
void operator+=( C<>& lhs, T rhs ) requires PushBackMovable<T, Alloc, C>
{
  lhs.push_back( std::forward<T>( rhs ) );
}

int main() {
  std::vector<int> ints;
  int a = 5;

  ints += 1;
  ints += a;

  std::copy(std::begin(ints), std::end(ints), std::ostream_iterator<int>(std::cout, " "));
}

Thank you for your help.

By using a template-template parameter you already constrain your function to containers that follow the standard library pattern but also to class templates that just happen to have two type template parameters. This is a strong constraint and usually useless in practice. That is, I would suggest that you instead operate on concrete types represented by a type template parameter, and let the concept verify any requirements that you would like to impose.

Additionally, don't use decltype with std::declval with std::is_same to check validity of an expression. Concepts have a dedicated syntax for that purpose, which is just putting this expression in braces.

One solution, merging what you want to verify in a single concept, could be as below:

#include <concepts>
#include <utility>
#include <type_traits>

template <typename C, typename T>
concept PushBackMovable = requires (C c, T t) {
    { c.push_back(t) } -> std::same_as<void>;
    { c.push_back(std::move(t)) } -> std::same_as<void>;
};

template <typename T, PushBackMovable<std::remove_reference_t<T>> C>
void operator+=(C& lhs, T&& rhs)
{
    lhs.push_back(std::forward<T>(rhs));
}

DEMO

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM