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
FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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
FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(367,27):
message : could be ‘bool std::operator ==(const std::pair<_Ty1,_Ty2>
&,const std::pair<_Ty1,_Ty2> &)’ 1>C:Program FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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 FilesMicrosoft
Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(367,27):
message : or ‘bool std::operator ==(const std::pair<_Ty1,_Ty2>
&,const std::pair<_Ty1,_Ty2> &)’ 1>C:Program FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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 FilesMicrosoft
Visual
Studio2022CommunityVCToolsMSVC14.35.32215includetuple(672,27):
message : or ‘bool std::operator ==(const std::tuple<_Types…>
&,const std::tuple<_Types…> &)’ 1>C:Program FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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 FilesMicrosoft
Visual
Studio2022CommunityVCToolsMSVC14.35.32215includexutility(1441,5):
message : or ‘bool std::operator ==(const
std::reverse_iterator<_BidIt> &,const std::reverse_iterator<_BidIt2>
&) noexcept()’ 1>C:Program FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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 FilesMicrosoft Visual Studio2022CommunityVCToolsMSVC14.35.32215includexutility(3969,5):
message : or ‘bool std::operator ==(const
std::move_iterator<_Iter> &,const std::move_iterator<_Iter2> &)
noexcept()’ 1>C:Program FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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 FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includexmemory(894,30):
message : or ‘bool std::operator ==(const std::allocator<_Ty>
&,const std::allocator<_Other> &) noexcept’ 1>C:Program
FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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
FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includemap(399,17):
message : or ‘bool std::operator ==(const
std::map<_Kty,_Ty,_Pr,_Alloc> &,const std::map<_Kty,_Ty,_Pr,_Alloc>
&)’ 1>C:Program FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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 FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includemap(630,17):
message : or ‘bool std::operator ==(const
std::multimap<_Kty,_Ty,_Pr,_Alloc> &,const
std::multimap<_Kty,_Ty,_Pr,_Alloc> &)’ 1>C:Program FilesMicrosoft
Visual
Studio2022CommunityVCToolsMSVC14.35.32215includeutility(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 FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includexstddef(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 FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includexutility(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
FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includexutility(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 FilesMicrosoft Visual
Studio2022CommunityVCToolsMSVC14.35.32215includemap(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:ProjectTemplateTestTemplateTestmain.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:ProjectTemplateTestTemplateTestmain.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:ProjectTemplateTestTemplateTestmain.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:ProjectTemplateTestTemplateTestmain.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)
2
Answers
When the
if constexpr
succeeds, the next statement is an equality comparison that doesn’t exist forstd::map
. Theconcept_map
keyword which was dropped fromconcept
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:Then you can define a custom comparison:
But before that, I need to define
concept instance_of
:Finally you can use your Wrapper:
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.
The problem with your code is that your
is_comparable<>
specialization forstd::map
is conditionally enabled for compilation only when the type is comparable. For non-comparable types, which is the case withstd::map<int, std::any>
, it falls back to the more generic specialization which evaluates totrue
. Here is how you can fix that: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 withstd::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.