Reputation: 13
I have a problem while defining a template class containing T.
namespace
{
template <typename T1, typename T2 = T1, typename Test = bool>
struct is_comparable : std::integral_constant<bool, false> {};
template <typename T1, typename T2>
struct is_comparable<T1, T2, decltype(std::declval<const std::decay_t<T1>>() == std::declval<const std::decay_t<T2>>())> : std::integral_constant<bool, true> {};
}
template <class T>
class Wrapper
{
public:
Wrapper() = default;
void set(const T& val)
{
if constexpr (is_comparable<T>::value)
{
if (_val == val)
return;
}
_val = val;
onChanged();
}
void onChanged() { ... }
private:
T _val;
};
The problem arises for std::map with a non-comparable value_type.
void foo()
{
Wrapper<std::map<int, std::any>> mapWrapper;
mapWrapper.set(std::map<int, std::any>()); // error occurs.
}
here's the cause.
static_assert(is_comparable<int>::value == true); // ok
static_assert(is_comparable<std::any>::value == false); // ok
static_assert(is_comparable<std::map<int, std::any>>::value == false); // error. I'm expecting false, but it's true.
// some code blocks in void Wrapper<T>::set(const T& val)
if constexpr (is_comparable<T>::value)
{
// enter here when T == std::map<int, std::any>
// and compile time error occurs.
if (_val == val)
return;
}
so I defined like this for avoiding the error, but is_comparable<T1, T2> is still used instead.
template <typename K1, typename V1, typename K2, typename V2>
struct is_comparable<std::map<K1, V1>, std::map<K2, V2>, std::enable_if_t<is_comparable<K1, K2>::value && is_comparable<V1, V2>::value, bool>> : std::integral_constant<bool, true> {};
is there any good resolution?
edit: full source codes and error messages is here.
#include <type_traits>
#include <map>
#include <any>
namespace
{
template <typename T1, typename T2 = T1, typename Test = bool>
struct is_comparable : std::integral_constant<bool, false> {};
template <typename T1, typename T2>
struct is_comparable<T1, T2, decltype(std::declval<const std::decay_t<T1>>() == std::declval<const std::decay_t<T2>>())> : std::integral_constant<bool, true> {};
template <typename K1, typename V1, typename K2, typename V2>
struct is_comparable<std::map<K1, V1>, std::map<K2, V2>, std::enable_if_t<is_comparable<K1, K2>::value&& is_comparable<V1, V2>::value, bool>> : std::integral_constant<bool, true> {};
} // unnamed namespace
template <class T>
class Wrapper
{
public:
Wrapper() = default;
void set(const T& val)
{
if constexpr (is_comparable<T>::value)
{
if (_val == val)
return;
}
_val = val;
onChanged();
}
void onChanged() { /* any expensive operations. */ }
private:
T _val;
};
static_assert(is_comparable<int>::value == true);
static_assert(is_comparable<std::any>::value == false);
// static_assert(is_comparable<std::map<int, std::any>>::value == false); // error
void foo()
{
Wrapper<std::map<int, std::any>> mapWrapper;
mapWrapper.set(std::map<int, std::any>()); // error occurs.
}
int main(int /*argc*/, char** /*argv*/)
{
foo();
return 0;
}
messages
Build started... 1>------ Build started: Project: TemplateTest, Configuration: Debug x64 ------ 1>main.cpp 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,56): error C2676: binary '==': 'const _Ty2' does not define this operator or a conversion to a type acceptable to the predefined operator 1>
with 1> [ 1> _Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(367,27): message : could be 'bool std::operator ==(const std::pair<_Ty1,_Ty2> &,const std::pair<_Ty1,_Ty2> &)' 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,48): message : 'bool std::operator ==(const std::pair<_Ty1,_Ty2> &,const std::pair<_Ty1,_Ty2> &)': could not deduce template argument for 'const std::pair<_Ty1,_Ty2> &' from 'const _Ty2' 1> with 1>
[ 1> _Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(367,27): message : or 'bool std::operator ==(const std::pair<_Ty1,_Ty2> &,const std::pair<_Ty1,_Ty2> &)' 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,48): message : 'bool std::operator ==(const std::pair<_Ty1,_Ty2> &,const std::pair<_Ty1,_Ty2> &)': could not deduce template argument for 'const std::pair<_Ty1,_Ty2> &' from 'const _Ty2' 1> with 1>
[ 1> _Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\tuple(672,27): message : or 'bool std::operator ==(const std::tuple<_Types...> &,const std::tuple<_Types...> &)' 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,48): message : 'bool std::operator ==(const std::tuple<_Types...> &,const std::tuple<_Types...> &)': could not deduce template argument for 'const std::tuple<_Types...> &' from 'const _Ty2' 1> with 1>
[ 1> _Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\xutility(1441,5): message : or 'bool std::operator ==(const std::reverse_iterator<_BidIt> &,const std::reverse_iterator<_BidIt2> &) noexcept()' 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,48): message : 'bool std::operator ==(const std::reverse_iterator<_BidIt> &,const std::reverse_iterator<_BidIt2> &) noexcept()': could not deduce template argument for 'const std::reverse_iterator<_BidIt> &' from 'const _Ty2' 1> with 1> [ 1>
_Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\xutility(3969,5): message : or 'bool std::operator ==(const std::move_iterator<_Iter> &,const std::move_iterator<_Iter2> &) noexcept()' 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,48): message : 'bool std::operator ==(const std::move_iterator<_Iter> &,const std::move_iterator<_Iter2> &) noexcept()': could not deduce template argument for 'const std::move_iterator<_Iter> &' from 'const _Ty2' 1> with 1> [ 1> _Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\xmemory(894,30): message : or 'bool std::operator ==(const std::allocator<_Ty> &,const std::allocator<_Other> &) noexcept' 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,48): message : 'bool std::operator ==(const std::allocator<_Ty> &,const std::allocator<_Other> &) noexcept': could not deduce template argument for 'const std::allocator<_Ty> &' from 'const _Ty2' 1>
with 1> [ 1> _Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\map(399,17): message : or 'bool std::operator ==(const std::map<_Kty,_Ty,_Pr,_Alloc> &,const std::map<_Kty,_Ty,_Pr,_Alloc> &)' 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,48): message : 'bool std::operator ==(const std::map<_Kty,_Ty,_Pr,_Alloc> &,const std::map<_Kty,_Ty,_Pr,_Alloc> &)': could not deduce template argument for 'const std::map<_Kty,_Ty,_Pr,_Alloc> &' from 'const _Ty2' 1> with 1> [ 1> _Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\map(630,17): message : or 'bool std::operator ==(const std::multimap<_Kty,_Ty,_Pr,_Alloc> &,const std::multimap<_Kty,_Ty,_Pr,_Alloc> &)' 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\utility(368,48): message : 'bool std::operator ==(const std::multimap<_Kty,_Ty,_Pr,_Alloc> &,const std::multimap<_Kty,_Ty,_Pr,_Alloc> &)': could not deduce template argument for 'const std::multimap<_Kty,_Ty,_Pr,_Alloc> &' from 'const _Ty2' 1> with 1> [ 1> _Ty2=std::any 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\xstddef(212): message : see reference to function template instantiation 'bool std::operator ==<const int,std::any>(const std::pair<const int,std::any> &,const std::pair<const int,std::any> &)' being compiled 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\xutility(4964): message : see reference to function template instantiation 'bool std::equal_to::operator ()<const std::pair<const int,std::any>&,const std::pair<const int,std::any>&>(_Ty1,_Ty2) noexcept(false) const' being compiled 1> with 1> [ 1>
_Ty1=const std::pair<const int,std::any> &, 1> _Ty2=const std::pair<const int,std::any> & 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\xutility(4995): message : see reference to function template instantiation 'bool std::equal<_InIt1,_InIt2,std::equal_to>(const _InIt1,const _InIt1,const _InIt2,_Pr)' being compiled 1> with 1> [ 1> _InIt1=std::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const int,std::any>>>,std::_Iterator_base0>, 1>
_InIt2=std::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const int,std::any>>>,std::_Iterator_base0>, 1>
_Pr=std::equal_to 1> ] 1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\map(399): message : see reference to function template instantiation 'bool std::equal<std::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const int,std::any>>>,std::_Iterator_base0>,std::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const int,std::any>>>,std::_Iterator_base0>>(const _InIt1,const _InIt1,const _InIt2)' being compiled 1> with 1> [ 1> _InIt1=std::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const int,std::any>>>,std::_Iterator_base0>, 1>
_InIt2=std::_Tree_unchecked_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const int,std::any>>>,std::_Iterator_base0> 1> ] 1>C:\Project\TemplateTest\TemplateTest\main.cpp(26,1): message : see reference to function template instantiation 'bool std::operator ==<int,std::any,std::less,std::allocator<std::pair<const int,std::any>>>(const std::map<int,std::any,std::less,std::allocator<std::pair<const int,std::any>>> &,const std::map<int,std::any,std::less,std::allocator<std::pair<const int,std::any>>> &)' being compiled 1>C:\Project\TemplateTest\TemplateTest\main.cpp(23,1): message : while compiling class template member function 'void Wrapperstd::map<int,std::any,std::less<int,std::allocator<std::pair<const int,std::any>>>>::set(const T &)' 1> with 1> [ 1>
T=std::map<int,std::any,std::less,std::allocator<std::pair<const int,std::any>>> 1> ] 1>C:\Project\TemplateTest\TemplateTest\main.cpp(44,19): message : see reference to function template instantiation 'void Wrapperstd::map<int,std::any,std::less<int,std::allocator<std::pair<const int,std::any>>>>::set(const T &)' being compiled 1> with 1>
[ 1>
T=std::map<int,std::any,std::less,std::allocator<std::pair<const int,std::any>>> 1> ] 1>C:\Project\TemplateTest\TemplateTest\main.cpp(43,38): message : see reference to class template instantiation 'Wrapperstd::map<int,std::any,std::less<int,std::allocator<std::pair<const int,std::any>>>>' being compiled 1>Done building project "TemplateTest.vcxproj" -- FAILED. ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========== ========== Build started at 5:43 PM and took 00.914 seconds ==========
(tried in msvc++ 14.35 / C++17)
Upvotes: 1
Views: 255
Reputation: 3122
The problem with your code is that your is_comparable<>
specialization for std::map
is conditionally enabled for compilation only when the type is comparable. For non-comparable types, which is the case with std::map<int, std::any>
, it falls back to the more generic specialization which evaluates to true
. Here is how you can fix that:
template<class ...> using void_t = void;
template<typename L, typename R = L, class = void>
struct is_comparable : std::false_type {};
template<typename L, typename R>
using comparability = decltype(std::declval<L>() == std::declval<R>());
template<typename L, typename R>
struct is_comparable<L, R, void_t<comparability<L, R>>> : std::true_type {};
// std::map<> specialization
template<typename K1, typename V1, typename K2, typename V2>
struct is_comparable<std::map<K1, V1>, std::map<K2, V2>>
: std::integral_constant<bool,
is_comparable<K1, K2>::value &&
is_comparable<V1, V2>::value
> {};
static_assert(is_comparable<int>::value == true);
static_assert(is_comparable<std::any>::value == false);
static_assert(is_comparable<std::map<int, float>>::value == true);
static_assert(is_comparable<std::map<int, std::any>>::value == false);
Godbolt: https://godbolt.org/z/77bs6re7e
On a more general note, the unfortunate answer is that it seems impossible to implement such is_comparable<>
trait in C++17 without defining specializations for each specific type (like you did with std::map
).
The reason for this is because the decltype(std::declval<L>() == std::declval<R>())
trick shown here only checks that the declaration is well formed but doesn't check that the comparison operator is actually well defined.
Even with C++20 std::equality_comparable<> concept we bump into the same problem. This answer explains that it would work if the C++ standard library had defined the right constraints on the container comparison operator, which it currently doesn't.
Upvotes: 2
Reputation: 4249
When the if constexpr
succeeds, the next statement is an equality comparison that doesn't exist for std::map
. The concept_map
keyword which was dropped from concept
proposal, might come in handy in such designs. Nevertheless, a closer look at STL reveals that a we can use a comparator as template parameter:
template <typename T, typename C>
class Wrapper
{
public:
void set(const T& val)
{
if (C comp;comp(val, this->val_))
return;
//...
Then you can define a custom comparison:
#include <concepts>
struct my_eq{
constexpr bool operator()(auto const& lhs, auto const& rhs)
requires std::equality_comparable_with<decltype(lhs), decltype(rhs)>
{ return rhs == lhs; };
template<instance_of<std::map> L, instance_of<std::map> R>
//requires std::equality_comparable_with<typenameR::mapped_type, typename L::mapped_type>
constexpr bool operator()(L const& lhs, R const& rhs)
{ /*TODO: define map comparison*/ };
};
But before that, I need to define concept instance_of
:
template<typename type, template<typename...> typename generic> struct instance_of_traits;
//TODO: use `std::same_as` to define the above
template<typename type, template<typename...> typename generic>
concept instance_of = bool{instance_of_traits<type, generic>::value};
Finally you can use your Wrapper:
Wrapper<std::map<int, std::any>, my_eq> mapWrapper;
However comparability of std::any
should also be carefully handled too.
Element-wise comparison of containers may negatively impact runtime, if used frequently. I guess you might decide another strategy after all.
Upvotes: 0