一个 通用的 add 设施 以及 is_container

写了一个通用的 add 设施, 原型如下:

  1. add(container_output, container_input) // 重载 1
    效果: 把 container_input 里的所有元素加到(push_back) container_output 里
  2. add(container_output, elements…) // 重载 2
    效果: 把所有 elements 加到(push_back) container_output 里

实现上, 使用 enable_if 做函数决议. 为此写了一个 is_container 设施判断类型是否是 container. 该设施部分遵循 Container Concept 的定义. 主要是构造啊 swap 啊几个操作符啊查起来太麻烦了. 所以没加.

首先是 is_container 的实现:

pp_cat.h, 做字符拼接.

#ifndef BOOST_PP_CAT
#    define BOOST_PP_CAT(a, b) BOOST_PP_CAT_I(a, b)
#    define BOOST_PP_CAT_I(a, b) a ## b
#endif

has_xxx.h

如名所述, 判断类型 T 是否有 T::xxx 这个东西. 比如 has_value_type<vector<int>> 是 true, has_value_type<std::shared_ptr<int>> 则是 false

  • 注意没有 include guard. 这个是预处理迭代模板用
  • 注意把 reference 去掉, 因为 pointer to reference is not allowed
#ifdef TNAME
#include <type_traits>
#include "pp_cat.h"

template <class T>
struct BOOST_PP_CAT(has_, TNAME) {
        template <class U> static constexpr bool test(...) { return false; }
        template <class U> static constexpr bool test( std::remove_reference_t<typename U::TNAME> *) { return true;}

        static constexpr bool value = test<T>(0);
};

template <class T>
constexpr bool BOOST_PP_CAT(BOOST_PP_CAT(has_, TNAME), _v) = BOOST_PP_CAT(has_, TNAME)<T>::value;

#undef TNAME
#endif

has_mem.h

作用基本同上, 但是是判断是否有成员(变量或者成员函数)的. 比如 has_mem_begin<vector<int> 是 true, has_mem_begin<shared_ptr<int>> 就是 false.

代码照抄自 more c++ idioms

#ifdef TNAME
#include "pp_cat.h"

/**
 * copy from wikibook
 **/
template <class T>
struct BOOST_PP_CAT(has_mem_, TNAME) {
        struct Fallback {int TNAME;};
        struct D : T, Fallback {};
        template <typename U, U> struct check;

        template <class> static constexpr bool test(...) { return true; }
        template <class U> static constexpr bool test(check<int Fallback::*, &U::TNAME>*) { return false; }

        static constexpr bool value = test<D>(0);
};

template <class T>
constexpr bool BOOST_PP_CAT(BOOST_PP_CAT(has_mem_, TNAME), _v) = BOOST_PP_CAT(has_mem_, TNAME)<T>::value;
#undef TNAME
#endif

然后是 is_container.hpp

基本上就是查了一番它 typedef 的类型和成员函数.
有一点比较奇怪的是 is_container_v 在模板里面不能用. 也不明白是为什么.

#include <type_traits>

namespace detail {
// a preprocess iteration will be better. I an not able to and not interested in making this done
#define TNAME value_type // value type
#include "has_xxx.h"
#define TNAME reference // reference
#include "has_xxx.h"
#define TNAME const_reference // const_reference
#include "has_xxx.h"
#define TNAME iterator // iterator
#include "has_xxx.h"
#define TNAME const_iterator // const_iterator
#include "has_xxx.h"
#define TNAME difference_type // difference_type
#include "has_xxx.h"
#define TNAME size_type // size_type
#include "has_xxx.h"
#define TNAME begin // begin
#include "has_mem.h"
#define TNAME cbegin // cbegin
#include "has_mem.h"
#define TNAME end // end
#include "has_mem.h"
#define TNAME cend // cend
#include "has_mem.h"
#define TNAME size // size
#include "has_mem.h"
#define TNAME max_size // max_size
#include "has_mem.h"
#define TNAME empty // empty
#include "has_mem.h"
}

template <bool, class>
struct is_container_impl {
        static constexpr bool value = false;
};

template <class T>
struct is_container_impl<true, T> {
        static constexpr int value =
                   detail::has_value_type_v<T>
                && detail::has_reference_v<T>
                && detail::has_const_reference_v<T>
                && detail::has_const_iterator_v<T>
                && detail::has_difference_type_v<T>
                && detail::has_size_type_v<T>
                && detail::has_mem_begin_v<T>
                && detail::has_mem_cbegin_v<T>
                && detail::has_mem_end_v<T>
                && detail::has_mem_cend_v<T>
                && detail::has_mem_size_v<T>
                && detail::has_mem_max_size_v<T>
                && detail::has_mem_empty_v<T>;
};

template <class T>
struct is_container : is_container_impl<std::is_class<T>::value, T> { };

template <class T>
constexpr bool is_container_v = is_container<T>::value;

这样一个 is_container 设施就算好了. 测试代码如下:

#include <iostream>
#include <vector>
#include <map>
#include <queue>
#include <deque>
#include <bitset>
#include <array>
#include "is_container.hpp"

struct foo {
        typedef int value_type;
        void begin();
};

struct bar {};

class my_intv {
        public:
                typedef int value_type;
                typedef value_type& reference;
                typedef const value_type& const_reference;
                typedef int* iterator;
                typedef const iterator const_iterator;
                typedef iterator difference_type;
                typedef int size_type;
        public:
                iterator begin();
                const_iterator cbegin() const;
                const_iterator begin() const;
                iterator end();
                const_iterator cend() const;
                const_iterator end() const;
                size_type size() const;
                size_type max_size() const;
                bool empty() const;
};

class my_intv_fake_t : my_intv {
        private:
                typedef int size_type;
};

class my_intv_fake_m : my_intv {
        private:
                size_type size() const;
};

using namespace std;

int main() {
        using T = vector<int>;
        using arr = int[10];
        cout << "intt" << is_container_v<int> << endl;
        cout << "vectort" << is_container_v<const vector<int>> << endl;
        cout << "dequet" << is_container_v<deque<int>> << endl;
        cout << "queuet" << is_container_v<queue<int>> << endl;
        cout << "bitsett" << is_container_v<bitset<12>> << endl;
        cout << "mapt" << is_container_v<map<int, char>> << endl;
        cout << "arrayt" << is_container_v<array<int, 12>> << endl;
        cout << "my_intvt" << is_container_v<my_intv> << endl;
        cout << "my_intv_fake_tt" << is_container_v<my_intv_fake_t> << endl;
        cout << "my_intv_fake_mt" << is_container_v<my_intv_fake_m> << endl;
}

最后用 enable_if 实现 generic add.

原型 1:

add(ContainerOut& out, const ContainerIn& in)

条件是

  1. ContainerOut 是 Container 类型(通过 is_container 判断)
  2. ContainerIn 是 Container 类型或者 array 类型(通过 is_array 判断)
  3. ContainerIn 的 value_type 能转换成 ContainerOut 的 value_type (通过 is_convertible 判断)

原型 2:

add(ContainerOut& out, Elements&& ... in)

条件是

  1. ContainerOut 是 Container 类型
  2. 每一个 Element 都能转换成 ContainerOut 的 value_type

最终产物如下:

#include "is_container.hpp"
#include <algorithm>
#include <type_traits>

template <class T>
using rm_ref = std::remove_reference_t<T>;

template <class COut, class CIn>
std::enable_if_t<
        is_container<rm_ref<COut>>::value // Out type is a container
        && ( is_container<rm_ref<CIn>>::value || std::is_array<rm_ref<CIn>>::value ) // In type is a container or an array
        && ( std::is_array<rm_ref<CIn>>::value ? // value_type of in type is same or convertible to value_type of out type
                        std::is_convertible<std::remove_extent_t<rm_ref<CIn>>, typename rm_ref<COut>::value_type>::value // array
                  : std::is_convertible<typename rm_ref<CIn>::value_type, typename rm_ref<COut>::value_type>::value // container
           )
>
add(COut& out, const CIn& in) {
        std::copy(begin(in), end(in), back_inserter(out));
}

template <bool first, bool ... args>
struct all_true {
        static constexpr bool value = first ? all_true<args...>::value : false;
};

template <bool last>
struct all_true<last> {
        static constexpr bool value = last;
};

template <class COut, class ... Args>
        std::enable_if_t<
                is_container<rm_ref<COut>>::value // Out type is a container
                && all_true< // Arg types all convertible to outtype
                        std::is_convertible< rm_ref<Args>, typename rm_ref<COut>::value_type >::value...
                >::value
        >
add(COut& out, Args&& ... in) {
        // initializer list don't implement move semantics
        auto tmp = {static_cast<typename rm_ref<COut>::value_type>(in)...}; // Don't know best practice in this case
        std::copy(begin(tmp), end(tmp), back_inserter(out));
}

One thought on “一个 通用的 add 设施 以及 is_container

Leave a Reply

Your email address will not be published. Required fields are marked *