Reputation: 81
I have the function
// Helper to determine whether there's a const_iterator for T.
template <typename T>
struct hasConstIt {
private:
template<typename C> static char test(typename C::const_iterator*);
template<typename C> static int test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
// Check if a container contains an element.
template <typename Container, typename = std::enable_if_t<hasConstIt<Container>::value> >
bool contains(const Container & container, typename Container::value_type const & element) {
return std::find(container.begin(), container.end(), element) != container.end();
}
How can I specify that the container value type must be a specific type? Say I want the container to contain int
, then valid containers could be vector<int>
, deque<int>
etc.
Upvotes: 2
Views: 516
Reputation: 4655
You can use std::is_same
to check if a type is exaclty the same as a specified one. If you want to check both of your conditions at once, you can simply use logical operators in the std::enable_if
conditon:
template <typename Container, typename = std::enable_if_t<
(std::is_same<typename Container::value_type, int>::value &&
hasConstIt<Container>::value)
> >
bool contains(const Container & container, typename Container::value_type const & element) {
return std::find(container.begin(), container.end(), element) != container.end();
}
Contrary to the other answer, this is SINAE friendly. But if you only want to make sure it does not compile if the user makes a mistake I'd go with the other solution because it will give you way better error messages.
Example here.
If you can use C++20 you can use requires
to constrain your template types. This is both SFINAE-friendly and gives you a nice error message.
template<typename Container>
requires (
requires {
typename Container::const_iterator;
typename Container::value_type;
}
&& std::same_as<typename Container::value_type, int>
)
bool contains (const Container & container, typename Container::value_type const & element) {
return true;
}
Or, if you need to constrain multiple functions you can define a concept
template<typename Container>
concept good_container =
requires(Container c) {
typename Container::const_iterator;
typename Container::value_type;
}
&& std::same_as<typename Container::value_type, int>;
template<good_container Container>
bool contains (const Container & container, typename Container::value_type const & element) {
return true;
}
Example here.
Upvotes: 3
Reputation: 26362
hasConstIt
looks redundant here. It can be simplified using the std::void_t
trick. You can just write:
template <typename Container, typename = std::void_t<typename Container::const_iterator>>
bool contains(/* ... */);
If Container
has const_iterator
member type, it will be converted into void
by std:void_t
, if it doesn't, SFINAE will step in. This is a general technique, but in this particular case you can go further and omit std::void_t
:
template<typename Container, typename = typename Container::const_iterator>
bool contains(/* ... */);
To embed an additional constraint on the element type you can just add another dummy template parameter in the standard way:
template<typename Container,
typename = typename Container::const_iterator,
typename = std::enable_if_t<std::is_same_v<typename Container::value_type, int>>>
bool contains(/* ... */);
Upvotes: 1
Reputation: 3380
You can get it done by using the std::is_same_v
ans static_assert
.
template <typename Container>
bool contains(const Container& container, typename Container::value_type const& element) {
static_assert(std::is_same_v<typename Container::value_type, int>, "The container 'value_type' must be 'int'!");
// You don't have to explicitly check if there is a 'const_iterator' because the STL containers specify 'cbegin()' and 'cend()'
// which returns the 'Container::const_iterator'. So checking if these functions are there will be enough (for STL containers).
return std::find(container.cbegin(), container.cend(), element) != container.cend();
}
Now if the Container::value_type
is not int
, it'll throw a compiler error stating that The container 'value_type' must be 'int'!
Bonus: Your hasConstIt
can be written in a more better way (more readable and the IDE wont complain that the functions are undefined),
template <typename T>
struct hasConstIt {
private:
template<typename C> static constexpr bool test(typename C::const_iterator*) { return true; }
template<typename C> static constexpr bool test(...) { return false; }
public:
static constexpr bool value = test<T>(nullptr);
};
Now you can use another static_assert
to check if there's a const_iterator
for the Container
explicitly. This step is optional.
Upvotes: 3