类模板的条件无效成员函数(隐式实例化;显式实例化失败)

时间:2018-12-19 18:34:39

标签: c++ templates language-lawyer c++17

我创建了一个班级模板。根据其模板参数,它支持不同的操作。对于模板参数的几种组合,应实例化(隐式)类模板。对于某些组合,可能存在没有意义的成员函数(并且也不会编译)。但是,只要我不强制执行 explicit 实例化,一切似乎都可以正常工作。

现在,这些可怕的案例被称为“未指定”,“未定义”,“格式错误;无需诊断”,……。我绝对想避免这些事情。因此,我想请教如何应对这种情况。

以下是显示相同观察结果的示例。请注意,我对如何解决这个确切的玩具示例不太感兴趣。

#include <iostream>
#include <type_traits>

template<class T>
struct SingleSink {
  SingleSink(T) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

template<class T>
struct DoubleSink {
  DoubleSink(T, T) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

template<class T, int arity /*, some other stuff */>
struct SuperSink {
// This class shall do something special depending on, say, `arity`.
// Instead of partially specializing the whole class template (and introducing
// code duplication for the remaining functionality), let us externalize the 
// `arity`-dependent behavior to a special member.

  using Sink = std::conditional_t<
    arity == 1,
    SingleSink<T>,
    DoubleSink<T>
  >;

  Sink sink_;

// [some more data members that do not depend on `arity`]

// for a fixed `Sink` one of the following constructors should fail to compile
  SuperSink(T i) : sink_{i} {}
  SuperSink(T i, T j) : sink_{i, j} {}
// ... so these are what I call "conditionally invalid member functions".
};

// explicit instantiation yields error (deactivated by comments):
// template struct SuperSink<int, 1>;
// template struct SuperSink<int, 2>;

int main() {
// implicit instantiation works
  SuperSink<int, 1>{5};
  SuperSink<int, 2>{5, 6};

// these yield a compile error (as desired)
// SuperSink<int, 1>{5, 6};
// SuperSink<int, 2>{5};
}
  1. 如果我从来没有这些条件无效的成员函数是一个问题吗? 需要显式实例化?
  2. 如果是:检查显式实例是否有效?

2 个答案:

答案 0 :(得分:1)

我至少看到了这样的问题,即设计破坏了如下类型的特征,因此,当使用SFINAE决定要调用的构造函数时,您可能会遇到问题。

static_assert(!std::is_constructible<SuperSink<int, 1>, int, int>::value);

static_assert(!std::is_constructible<SuperSink<int, 2>, int>::value);

您可以通过反转设计来解决此问题:将SingleSinkDoubleSink定义为通用SuperSink的特殊情况(或者如果需要:特殊化)。

#include <iostream>
#include <type_traits>

template<class T, int arity>
struct SuperSink {

 template<typename... Ts, typename = std::enable_if_t<sizeof...(Ts) == arity> >
  SuperSink(Ts... is) {
       std::cout << __PRETTY_FUNCTION__ << std::endl;
  }

};


template<typename T>
struct SingleSink : SuperSink<T, 1> {
    using Base = SuperSink<T, 1>;
    using Base::Base; // inherit constructor
    // implement special functionality here
};


template<typename T>
struct DoubleSink : SuperSink<T, 2> {
    // follow same pattern as in SingleSink.
}


int main() {
    // implicit instantiation works
    SuperSink<int, 1>{5};
    SuperSink<int, 2>{5, 6};

    // Now, these work as desired
    static_assert(!std::is_constructible<SuperSink<int, 1>, int, int>::value);

    static_assert(!std::is_constructible<SuperSink<int, 2>, int>::value);

    // these yield a compile error (as desired)
    //  SuperSink<int, 1>{5, 6};
    // SuperSink<int, 2>{5};
}

答案 1 :(得分:1)

  

如果我不需要显式实例化,这些条件无效的成员函数是否会成为问题?

即使STL中的模板也具有“无效”方法,例如:std::vector<T>::resize(std::size_t)和不可默认构造的T

因此,使用“无效”方法,您的类可以正常使用。记录您的需求是一种选择。

但是,这些方法不是SFINAE友好的,因为错误不会立即出现在上下文中,而是会直接声明。

如果无效,您可以自行使用SFINAE删除它们,例如:

template <std::size_t N = arity, std::enable_if_t<N == 1, int> = 0>
SuperSink(T i) : sink_{i} {}

template <std::size_t N = arity, std::enable_if_t<N != 1, int> = 0>
SuperSink(T i, T j) : sink_{i, j} {}

在C ++ 2a中,您可以指定一些条件以在类中包括方法(类似于上述SFINAE,但语法更好,没有额外的模板):

SuperSink(T i) requires (arity == 1) : sink_{i} {}

SuperSink(T i, T j) requires (arity != 1) : sink_{i, j} {}