Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check at compile time if a template argument type is set or multiset, and element type of the container is arithmetic

I have a function named 'equal' accepting 2 parameters, each of them should be either std::set or std::multiset, and the element type of container should be arithmetic type (int, float, double ... ). I want the compiler report an error if above 2 conditions not satisfied.

I hope my code could run like this:

int main(void)
{
    std::set<int> s1;
    std::set<int> s2;
    equal(s1, s2);    //  OK

    std::multiset<float> s3;
    std::multiset<float> s4;
    equal(s3, s4);    //  OK

    std::set<int> s5;
    std::multiset<int> s6;
    equal(s5, s6);    //  compile error

    std::set<int*> s7;
    std::set<int*> s8;
    equal(s7, s8);    //  compile error

    std::vector<int> s9;
    std::vector<int> s10;
    equal(s9, s10);    //  compile error

    return 0;
}

And now it can check if element is arithmetic type, like below:

template <class Container, class = typename std::enable_if<std::is_arithmetic<typename Container::value_type>::value>::type>
bool equal(const Container &container1, const Container &container2)
{
    return true;
}

But how to make sure the container only be set or multiset ?

the compiler can support C++11, such as vc2015 or gcc4.8

like image 263
Royt Avatar asked Dec 08 '22 19:12

Royt


2 Answers

Create a is_set_or_multiset type trait that will use template specialization to match std::set<...> and std::multiset<...>:

template <typename>
struct is_set_or_multiset : std::false_type {};

template <typename... Ts>
struct is_set_or_multiset<std::set<Ts...>> : std::true_type {};

template <typename... Ts>
struct is_set_or_multiset<std::multiset<Ts...>> : std::true_type {};

Then use it as an additional condition in your enable_if clause:

template <class Container, 
          class = typename std::enable_if<
                                std::is_arithmetic<typename Container::value_type>::value
                             && is_set_or_multiset<Container>{}
                                >::type>
bool equal(const Container &container1, const Container &container2)
{
    return true;
}

live example on wandbox

like image 195
Vittorio Romeo Avatar answered May 15 '23 21:05

Vittorio Romeo


template<template<class...>class Z, class T>
struct is_instance_of_template : std::false_type {};

template<template<class...>class Z, class...Ts>
struct is_instance_of_template<Z,Z<Ts...>> : std::true_type {};

template<class Container>
using value_type_t = typename Container::value_type;

template <class Container,
    std::enable_if_t<
        std::is_arithmetic<value_type_t<Container>>{}
        && (
            is_instance_of_template<std::set, Container>{}
            || is_instance_of_template<std::multiset, Container>{}
        )
    >* =nullptr
>
bool equal(const Container &container1, const Container &container2)
{
  static_assert( std::is_arithmetic<value_type_t<Container>>{},
    "Container must contain arithmetic values"
  );
  static_assert(
    is_instance_of_template< std::set, Container >{}
    || is_instance_of_template< std::multiset, Container >{},
    "Container must be a set or multiset"
  );
  return true;
}

The static_asserts are just to verify the SFINAE above it. The SFINAE can be skipped if you don't need to have SFINAE -- if you are ok with hard build breaks instead of failure to match this overload.

Note that the enable_if_t<>* =nullptr technique doesn't work perfectly on some compilers, like MSVC2015, in my experience. On those compilers, use class=enable_if_t<>. I use enable_if_t<>* =nullptr because it gets rid of the "identical template signature" problem.

like image 38
Yakk - Adam Nevraumont Avatar answered May 15 '23 20:05

Yakk - Adam Nevraumont