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

发布于 2024-12-28 09:49:25 字数 1265 浏览 3 评论 0原文

我们有复杂的模板类,其中有一些方法不适用于某些策略或类型。因此,当我们检测到这些类型时(在编译时,使用类型特征),我们会触发一个带有良好消息的静态断言。

现在我们也做了很多手动模板实例化。部分原因是方法被迫编译器对方法进行语法检查。它还减少了库用户的编译时间。问题是静态断言总是被触发,因此我们无法手动实例化有问题的模板类。

有解决方法吗?

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

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

// source
template someClass<int>; // Explicit instantiation

编辑2:这是另一个示例。这次您可以首先立即编译代码,然后取消注释[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;
}

We have complex template classes that have some methods which will not work with certain policies or types. Therefore, when we detect those types (at compile time, using type-traits) we fire a static assertion with a nice message.

Now we do a lot of manual template instantiation as well. Partly it is so that the methods are forced to compiler to syntax check the methods. It also reduces compile time for the library user. The problem is that the static assertions are always fired and consequently we cannot manually instantiate the template class in question.

Is there a workaround for this?

EDIT: To make it clearer, here is an example (the explicit instantiation in this case will fail on someFunc1():

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

// source
template someClass<int>; // Explicit instantiation

EDIT2: Here is another example. This time you can compile it to see what I mean. First compile right away. The code should compile. Then Uncomment [2] and the static assertion should fire. Now comment out [2] and Uncomment [1]. The static assertion will fire because you are explicitly instantiating the template. I want to avoid removing explicit instantiation because of the benefits that come with it (see above for benefits).

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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

彻夜缠绵 2025-01-04 09:49:25

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

下面是一个简单的示例,展示了它的样子:

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()

You can't have both: you can't have a static assertion to prevent instantiation and explicitly instantiate the type! This is an obvious contradiction. What you can have, however, is conditionally included functionality even though it is somewhat a pain in the neck: If a certain member function is not supposed to be supported for certain types, you can move this function into a base class which conditionally has it. This way you wouldn't use a static assertion but just remove the member function. I realize that this introduces interesting other problems, e.g. with respect to the location of member variables, but I think in the context you are describing this is the best you can get.

Here is a quick example of how this could look like:

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()
墨落成白 2025-01-04 09:49:25

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

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

template<typename T>
struct Foo {
    void bar() {
        std::cout << "bar\n";
    }
    void baz() {
        std:: cout << "baz\n";
    }
};

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>
}

当客户端代码中出现错误时,静态断言不再有助于提供良好的错误消息,但您可能希望将它们保留下来,因为它们会如果您忘记提供专业化,则将被解雇。

Alright, so if you're forcing the instantiation of all methods using explicit instantiation, you can't get away with any compile time tricks to prevent instantiation of the offending methods, such as enable_if. It'd be easy enough to move the error to runtime, but that's undesirable.

I think the best you can do is move the error to link time, which will statically ensure that the program does not contain a code path that could potentially call the prohibited function, but the error messages won't be very helpful to anyone that doesn't know about the restriction you're imposing. Anyway, the solution is to declare a specialization of the prohibited member functions but not define them:

template<typename T>
struct Foo {
    void bar() {
        std::cout << "bar\n";
    }
    void baz() {
        std:: cout << "baz\n";
    }
};

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>
}

The static asserts no longer help give nice error messages when a mistake is made in client code, but you might want to leave them in because they'll fire if you forget to provide a specialization.

小苏打饼 2025-01-04 09:49:25

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;
}

enable_if is a flexible mechanism for precise template methods targeting, may be what you are after. Example:

#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;
}
岁月打碎记忆 2025-01-04 09:49:25

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

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

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;
}

在我的所有模板代码中,这些类型的函数(即具有静态断言的函数或在某些类型上工作,但可能通过使用类型特征而在其他类型上失败[在这种情况下,可以为不同类型选择几个不同的函数) ]) 对客户端隐藏。因此,在我的实现中,添加额外的虚拟参数是一个不错的折衷方案。

作为奖励,它让我知道这个函数被设计为仅由某些类型使用。此外,我最初的显式实例化问题通过这种简单的技术得到了解决。

I did not get an opportunity to test enable_if as suggested by bobah but I did come up with a solution that does not require boost and that satisfies my original requirement to a good extent (I say good and not full, will explain at the end)

The solution is to put a dummy template on the code that will fail if compiled under some selected types and is fine under others. So:

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;
}

In all of my template code, these kind of functions (i.e. the ones that have static asserts OR work on some types and may fail on others by using type traits [in which case there is a selection of several different functions for different types]) are hidden from the client. So in my implementation, adding the extra dummy parameter is an OK compromise.

As a bonus, it lets me know that this function is designed to be used by only certain types. Furthermore, my original problem of explicit instantiation is solved by this simple technique.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文