我可以从手动模板实例化中排除一些方法吗

Can I exclude some methods from manual template instantiation?

本文关键字:排除 方法 实例化 我可以      更新时间:2023-10-16

我们有一些复杂的模板类,它们有一些方法不能与某些策略或类型一起使用。因此,当我们检测到这些类型时(在编译时,使用类型特征),我们会发出一个带有漂亮消息的静态断言。

现在我们也进行了大量的手动模板实例化。部分原因是这些方法被强制编译来对这些方法进行语法检查。它还减少了库用户的编译时间。问题是静态断言总是被激发,因此我们无法手动实例化有问题的模板类。

有解决办法吗?

EDIT:为了更清楚,这里有一个例子(在这种情况下,显式实例化将在someFunc1()上失败:

// header
template <typename T>
class someClass
{
  void someFunc() {}
  void someFunc1() { static_assert(false, assertion_failed); }
};
// source
template someClass<int>; // Explicit instantiation

EDIT2:这里是另一个例子。这次你可以把它编译一下,看看我的意思。首先立即编译。代码应该编译。然后取消注释[2],静态断言应该被激发。现在注释出[2]取消注释[1]。静态断言将被激发,因为您正在显式实例化模板。我希望避免删除显式实例化,因为它带来了好处(参见上面的好处)。

namespace Loki
{
  template<int> struct CompileTimeError;
  template<> struct CompileTimeError<true> {};
}
#define LOKI_STATIC_CHECK(expr, msg) 
    { Loki::CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; }
template <typename T>
class foo
{
public:
  void func() {}
  void func1() { LOKI_STATIC_CHECK(sizeof(T) == 4, Assertion_error); }
};
template foo<int>;
//template foo<double>; // [1]
int main()
{
  foo<int> a;
  a.func1();
  foo<double> b;
  //b.func1(); //[2]
  return 0;
}

不能两者都有:不能有静态断言来阻止实例化显式实例化类型!这是一个明显的矛盾。然而,你所能拥有的是有条件包含的功能,尽管这有点令人头疼:如果某个成员函数不应该支持某些类型,你可以将该函数移动到有条件包含它的基类中。这样你就不会使用静态断言,而只会删除该成员函数。我意识到这会引入其他有趣的问题,例如成员变量的位置,但我认为在你描述的上下文中,这是你能得到的最好的问题。

下面是一个快速的例子,说明这可能是什么样子:

template <typename T, bool = std::numeric_limits<T>::is_integer> struct foo_base;
template <typename T> struct foo_base<T, false> { /* intentionally left blank */ };
template <typename T> struct foo_base<T, true> { void foo() { /*...*/ } };
template <typename T>
struct Foo: foo_base<T> { /* .... */ };
template struct Foo<int>;    // will have foo()
template struct Foo<double>; // will not have foo()

好吧,所以如果你使用显式实例化来强制实例化所有方法,那么你就无法逃脱任何编译时技巧来防止实例化有问题的方法,比如enable_if。将错误转移到运行时是很容易的,但这是不可取的。

我认为你能做的最好的事情是将错误转移到链接时间,这将静态地确保程序不包含可能调用被禁止函数的代码路径,但错误消息对任何不知道你施加的限制的人都没有太大帮助。无论如何,解决方案是声明被禁止的成员函数的专门化,但不定义它们:

template<typename T>
struct Foo {
    void bar() {
        std::cout << "barn";
    }
    void baz() {
        std:: cout << "bazn";
    }
};
template<> void Foo<int>::baz(); // use of Foo<int>::baz() will resolve to this specialization, and linking will fail 
template struct Foo<int>;
template struct Foo<char>;
int main() {
    Foo<int> f;
    f.bar();
    // f.baz(); // uncommenting this line results in an ugly link time error
    Foo<char> b;
    b.bar();
    b.baz();  // works with Foo<char>
}

当客户端代码中出现错误时,静态断言不再有助于提供漂亮的错误消息,但您可能希望保留它们,因为如果您忘记提供专门化,它们会被激发。

enable_if是一种灵活的机制,可以精确定位模板方法,这可能是您想要的。示例:

#include <string>
#include <iostream>
#include <boost/utility.hpp>
#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
template <class T> class mywrapper
{
  T _value;
  template <class V>
  typename boost::enable_if<boost::is_scalar<V>, void>::type printval_(V const& value)
  {
    BOOST_STATIC_ASSERT(boost::is_scalar<V>::value);
    std::cout << "scalar: " << value << std::endl;
  }
  template <class V>
  typename boost::enable_if<boost::is_compound<V>, void>::type printval_(V const& value)
  {
    BOOST_STATIC_ASSERT(boost::is_compound<V>::value);
    std::cout << "compound: " << value << std::endl;
  }
public:
  mywrapper(T const& value):_value(value) { }
  void printval() { printval_(_value); }
};
template class mywrapper<int>;
template class mywrapper<std::string>;
int main()
{
  mywrapper<int> ival(333);
  mywrapper<std::string> sval("test");
  ival.printval();
  sval.printval();
  return 0;
}

我没有机会按照bobah的建议测试enable_if,但我确实提出了一个不需要升压的解决方案,并且在很大程度上满足了我最初的要求(我说而不是,最后会解释)

解决方案是在代码上放置一个伪模板,如果在某些选定类型下编译,该模板将失败,而在其他类型下则可以。因此:

struct dummyStruct {};
#define DUMMY_TEMP typename dummy
#define DUMMY_PARAM dummyStruct
namespace Loki
{
  template<int> struct CompileTimeError;
  template<> struct CompileTimeError<true> {};
}
#define LOKI_STATIC_CHECK(expr, msg) 
{ Loki::CompileTimeError<((expr) != 0)> ERROR_##msg; (void)ERROR_##msg; }
template <typename T>
class foo
{
public:
  void func() {}
  template <typename T_Dummy>
  void func1() { LOKI_STATIC_CHECK(sizeof(T) == 4, Assertion_error); }
};
template foo<int>;
template foo<double>; // [1]
int main()
{
  foo<int> a;
  a.func1<DUMMY_PARAM>();
  foo<double> b;
  //b.func1<DUMMY_PARAM>(); //[2] - this is a static error
  return 0;
}

在我的所有模板代码中,这些类型的函数(即那些对某些类型具有静态断言或有效的函数,并且可能通过使用类型特征(在这种情况下,可以为不同类型选择几个不同的函数)而对客户端隐藏。因此,在我的实现中,添加额外的dummy parameter是一个不错的折衷方案。

额外的好处是,它让我知道这个功能是专为某些类型而设计的。此外,我最初的显式实例化问题就是通过这个简单的技术解决的。