Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I exclude some methods from manual template instantiation?

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;
}
like image 397
Samaursa Avatar asked Jan 21 '12 21:01

Samaursa


People also ask

How will you restrict the template for a specific datatype?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

Is it necessary to instantiate a template?

Class template instantiationIn order for any code to appear, a template must be instantiated: the template arguments must be provided so that the compiler can generate an actual class (or function, from a function template).

How the instantiation of function template happens?

When a function template is first called for each type, the compiler creates an instantiation. Each instantiation is a version of the templated function specialized for the type. This instantiation will be called every time the function is used for the type.

How many times is the template class instantiation?

Template instantiation has two forms: explicit instantiation and implicit instantiation.


3 Answers

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()
like image 66
Dietmar Kühl Avatar answered Oct 11 '22 20:10

Dietmar Kühl


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.

like image 26
bames53 Avatar answered Oct 11 '22 21:10

bames53


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;
}
like image 38
bobah Avatar answered Oct 11 '22 20:10

bobah