有条件地删除模板类中的方法

时间:2018-02-24 13:27:04

标签: c++ c++11 templates explicit-instantiation

背景:由于模板代码膨胀,STL的使用使构建变慢:通常,相同的方法在许多不同的翻译单元中独立实例化,被多次编译和优化。避免模​​板类的此对象代码重复的一种方法是使用显式模板实例化外部模板声明,但STL实现不支持它们。我正在尝试实现一个支持显式实例化的等效std::vector<T>

问题:我有一个模板类vector<T>,如果模板参数T不满足某些条件,我想要删除它的一些方法。 更糟糕的是,这里还有其他要求:

  1. 无论vector<T>是否满足条件,都必须能够明确地实例化T
  2. 当有人调用有条件删除的方法时,应该发出编译错误。
  3. 我有几个这样的条件,以及一组依赖于它们的方法。
  4. 讨论:例如,当vector<T>::push_back(const T&)不可复制时,方法T无效。

    如果我保持此方法实现不变,那么如果我明确地实例化例如,编译器将产生错误。 vector<unique_ptr<int>>,因为它无法复制unique_ptr<int>。这与要求1相矛盾,并且无法实现所需的代码膨胀优化。

    我可以创建两个不同的vector<T>::push_back(const T&)实现,一个用于复制可构造类型,另一个用于其他类型。这可以使用SFINAE重载或辅助模板类来完成。非可复制构造的情况的实现可以简单地抛出异常或调用终止。但是,为push_back调用方法vector<unique_ptr<int>>只会在运行时崩溃。它不会产生编译错误,如要求2中所述。

    最后,有几个条件,每个条件都排除了一些方法。类型T在各种组合中可能缺少以下属性:可复制构造,可复制,可默认构造,可构造移动。来自the related question的解决方案似乎只适用于一个条件,它不满足要求3。

    我唯一的想法是使用整个vector<T>与预处理程序黑客相结合的某种部分特化。但这需要16个独立的专业化,听起来很糟糕。

    P.S。对我来说很明显,STL设计本质上依赖于隐式实例化的机制,这使得很难减少由它们引起的代码膨胀。

2 个答案:

答案 0 :(得分:3)

您可以让重载解析和std::conditional发挥其魔力。这并不会受到指数量的专业化的影响,因为每种方法都只根据自己的要求来预测。

template<typename T>
class vector_errors
{
public:
    template<typename...>
    void push_back(const T&)
    {
        static_assert(std::is_copy_constructible_v<T>, "T must be copy constructible");
    }
};

class dummy
{
    dummy(const dummy&) {}  // disable construction
};

template<typename T>
class vector : vector_errors<T>
{
    using base = vector_errors<T>;
public:
    using base::push_back;
    void push_back(const std::conditional_t<std::is_copy_constructible_v<T>, T, dummy>& t)
    {
        if constexpr(std::is_copy_constructible_v<T>)
        {
            // do stuff
        }
    }
};

template class vector<int>;                   // instantiates push_back
template class vector<std::unique_ptr<int>>;  // error on push_back

Live

答案 1 :(得分:1)

您可以使用SFINAE。它完全符合您的要求:

struct X
{
    X() = default;
    X(const X&) = delete;
};


template <class T> struct V
{
    T* ptr_;


    template <class U = T, decltype(U{std::declval<U>()})* = nullptr>
    auto push_back(const T& val)
    {
        static_assert(std::is_same<U, T>::value, "explicit template parameter U provided.");
        // ... code
    }

    // you can have this or not, depending on your preferences
    // auto push_back(...) = delete;
};

// explicit instantiation OK
template struct V<int>;
// you then need to explicit instantiate all the method templates
template auto V<int>::push_back(const int&) -> void;

template struct V<X>; // explicit instantiation OK

// don't instantiate "disabled" methods
// template auto V<X>::push_back(const X&) -> void;


auto test()
{
    V<X> v;  // OK

    v.push_back(X{}); // compile time error
}

您可以显式实例化向量,可以创建对象,并在调用push_back时出现编译错误:

<source>:34:7: error: no matching member function for call to 'push_back'
    v.push_back(X{});
    ~~^~~~~~~~~
<source>:19:10: note: candidate template ignored: substitution failure [with U = X]: excess elements in struct initializer
    auto push_back(const U& val)
         ^

或者,如果您离开已删除的push_back,则会获得:

<source>:34:7: error: call to deleted member function 'push_back'
    v.push_back(X{});
    ~~^~~~~~~~~
<source>:26:10: note: candidate function has been explicitly deleted
    auto push_back(...) = delete;
         ^
<source>:19:10: note: candidate template ignored: substitution failure [with U = X]: excess elements in struct initializer
    auto push_back(const U& val)
         ^

唯一的麻烦是你需要显式地实例化所有方法模板,但只需要那些未被禁用的模板。