I have a container template class that internally aggregates a std::vector
of type T
, where T
is actually a pointer to type S
. I want to create a member function, insert_sorted()
, that is only implemented if the type S
, T
points to, actually supports operator<()
.
Sadly, I'm not very well versed in templates, except for basic stuff, and type traits are also more of an unknown country to me. I've been looking at many similarly looking questions here on SO and elsewhere on the Internet, but I'm simply too dumb to figure out the correct syntax. Seems I need to use std::enable_if
, but how? Ideally, I would like to have declaration and implementation separated. Can someone help? I'm using MSVC 2022 in C++17, btw.
Here's the declaration of the class as it stands (removed some unnecessary stuff). Everything besides insert_sorted()
compiles and works:
#include <vector>
#include <algorithm>
#include <type_traits>
#include <utility>
#include <assert.h>
// Disable warning 'C4251': class 'type1' needs to have dll-interface to be used by clients of class 'type2'
// Disable warning 'C4275': non dll-interface class used as base for dll-interface class
#pragma warning(disable:4251 4275)
namespace supports
{
namespace details { struct return_t {}; }
template<typename T>
details::return_t operator<(T const&, T const&);
template<typename T>
struct less_than : std::integral_constant<bool,
!std::is_same<decltype(std::declval<std::remove_pointer<T> const&>() < std::declval<std::remove_pointer<T> const&>()), details::return_t>::value>
{};
template<typename T>
struct greater_than : std::integral_constant<bool,
!std::is_same<decltype(std::declval<T const&>() > std::declval<T const&>()), details::return_t>::value>
{};
template<typename T>
struct equal : std::integral_constant<bool,
!std::is_same<decltype(std::declval<T const&>() == std::declval<T const&>()), details::return_t>::value>
{};
template<typename T>
struct not_equal : std::integral_constant<bool,
!std::is_same<decltype(std::declval<T const&>() != std::declval<T const&>()), details::return_t>::value>
{};
}
// PtrVector
template<typename T>
class PtrVector
{
public:
typedef typename std::vector<T>::value_type value_type;
typedef typename std::vector<T>::size_type size_type;
typedef typename std::vector<T>::reference reference;
typedef typename std::vector<T>::const_reference const_reference;
typedef typename std::vector<T>::pointer pointer;
typedef typename std::vector<T>::const_pointer const_pointer;
typedef typename std::vector<T>::iterator iterator;
typedef typename std::vector<T>::const_iterator const_iterator;
typedef typename std::vector<T>::reverse_iterator reverse_iterator;
typedef typename std::vector<T>::const_reverse_iterator const_reverse_iterator;
PtrVector(bool owner = true, size_type reserve = 0);
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
bool insert_sorted(const value_type& val, typename std::enable_if_t<supports::less_than<T>::value, int> = 0);
protected:
std::vector<T> m_container;
};
// PtrVector implementation
template<typename T>
bool PtrVector<T>::insert_sorted(const value_type& val, typename std::enable_if_t<supports::less_than<T>::value, int>)
{
auto it = std::lower_bound(begin(), end(), val, [](const value_type& a, const value_type& b)->bool { return *a < *b; });
if (it == end() || *val < *(*it))
{
m_container.insert(it, val);
return true;
}
return false;
}
I've now found a working solution where the member function is implemented in the class declaration. However, I cannot seem to be able to split the code into declaration and implementation. To be honest, it's not necessary, but it would look nicer.
template <typename = std::enable_if_t<supports::less_than<T>, int>>
bool insert_sorted(const value_type& val)
{
auto it = std::lower_bound(begin(), end(), val, [](const value_type& a, const value_type& b)->bool { return *a < *b; });
if (it == end() || *val < *(*it))
{
m_container.insert(it, val);
return true;
}
return false;
}
Ideally, I would like something like this, but the implementation gets flagged as an error (declaration doesn't match):
template<typename = std::enable_if_t<supports::less_than<T>, int>>
bool insert_sorted(const value_type& val);
template<typename T, typename = std::enable_if_t<supports::less_than<T>, int>>
bool PtrVector<T>::insert_sorted(const value_type& val)
{
auto it = std::lower_bound(begin(), end(), val, [](const value_type& a, const value_type& b)->bool { return *a < *b; });
if (it == end() || *val < *(*it))
{
m_container.insert(it, val);
return true;
}
return false;
}
Sad to see this question has been downvoted. While I'm certainly not here for the votes, I feel as if this belittles my question. I'm genuinely interested in learning new stuff, and most of this is very new to me.
Thanks to Remy for cleaning up the text.
You have several issues:
template<typename T>
struct less_than : std::integral_constant<bool,
!std::is_same<decltype(std::declval<std::remove_pointer<T> const&>() < std::declval<std::remove_pointer<T> const&>()), details::return_t>::value>
{};
For your intent, std::remove_pointer<T>
should be std::remove_pointer_t<T>
.
but better IMO to use std::remove_pointer_t<T>
for the caller
For SFINAE to work on method, the condition should be from template method, not class method.
Moreover typename = std::enable_if_t<supports::less_than<T>, int>
has caveat as it disallows overload; right way is std::enable_if_t<supports::less_than<T>, int> = 0
So it becomes
template <typename U = std::remove_pointer_t<T>,
std::enable_if_t<supports::less_than<U>::value, int> = 0>
bool insert_sorted(const value_type& val);
and for out of class definition:
template <typename PTR>
template <typename U, std::enable_if_t<supports::less_than<U>::value, int>>
bool PtrVector<PTR>::insert_sorted(const value_type& val)
{
// ...
}
The check inside insert_sorted
is suspicious, if it is not sorted, then it is UB to call std::lower_bound