skip to Main Content

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


  1. 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.

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search