简体   繁体   中英

Prevent templated member function from being instantiated for a given type

I have a templated matrix class that I explicitly instantiate for various POD types and custom class types. Some of the member functions however don't make sense for a few of such custom types. For example:

Matrix<int> LoadFile(....); // This makes sense
Matrix<My_custom_class> LoadFile(...); //This doesn't make sense in the context of the custom class

Can I prevent the instantiation of the LoadFile function (which is a member function) for Matrix objects of select types? So far I have avoided the issue by making LoadFile a friend function and then explicitly controlling its instantiation. But I want to know if I can do this when LoadFile is a member function of Matrix .

The first question is whether you really need to control this. What happens if they call that member function on a matrix that stores My_custom_class ? Can you provide support in your class (or the template) so that the member function will work?

If you really want to inhibit the use of those member functions for some particular type, then you can use specialization to block the particular instantiation:

template <typename T>
struct test {
   void foo() {}
};
template <>
inline void test<int>::foo() = delete;

Or even just add static_assert s to the common implementation verifying the preconditions for what types is it allowed or disallowed?

template <typename T>
struct test {
   void foo() {
       static_assert(std::is_same<T,int>::value || std::is_same<T,double>::value,
                     "Only allowed for int and double");
       // regular code
   }
};

with std::enable_if , this is the best I can come up with

template< typename T >
struct Matrix {
    template< typename T >
    Matrix< typename std::enable_if<std::is_integral<T>::value, T>::type >
        LoadFile() 
    {
        return Matrix<T>();
    }
};

Matrix<int> a;
Matrix<int> b = a.LoadFile<int>()

only type int compile while other don't.

Can I prevent the instantiation of the LoadFile function (which is a member function) for Matrix objects of select types?

Your best bet here would be to use a static_assert that would create a compiler error when you attempt to call the method in a version of the class instantiated with a blocked type. Using std::enable_if , and other methods that would selectively "disable" a method itself would require you to create partial or full specializations of the class with and without the methods in question in order to prevent compiler errors. For instance, AFAIK, you cannot do the following:

template <typename T>
struct test
{
    static const bool value = false;
};

template<>
struct test<double>
{
    static const bool value = true;
};


template<typename T>
struct example
{
    void print() { cout << "Printing value from print()" << endl; }

    typename enable_if<test<T>::value, T>::type another_print() 
    { 
        cout << "Printing value from another_print()" << endl;
        return T(); 
    }
};

If you attempted to instantiate an example<int> , etc., you would end up with a compiler error at the point of instantiation of the object type. You couldn't simply call example<int>::print() and be okay, and only run into a problem if you chose to call example<int>::another_print() . Specializations of example<T> could get you around the issue, but that can be a bit of a mess. As originally surmised, a static_assert would probably be the easiest case to handle, along with a nice message to the end-user explaining what went wrong.

Keep in mind that creating compiler errors is the goal, and it's a good one to have. If you blocked a method from being instantiated, and the end-user decided to invoke it, you'd end up with a compiler error either way. The version without the static_assert will leave a lot of head-scratching as the user of your class attempts to parse a probably very verbose compiler error message, where-as the static_assert method is direct and to the point.

If the selected set of types is known at compile time, and you are using c++11 with a compiler that supports type aliases , uniform initialization and constexpr (for example gcc 4.7) you can make your code a bit cleaner like this (from previous example above by yngum):

template <bool Cond, class T = void>
using enable_if_t = typename std::enable_if<Cond, T>::type;

    template< typename T >
    struct Matrix {

    template< typename T >
    //std::is_integral has constexpr operator value_type() in c++11. This will work thanks to uniform init + constexpr. With the alias, no more need for typename + ::type
    Matrix<enable_if_t<std::is_integral<T>{}>>
    LoadFile() 
    {
        return Matrix<T>();
    }
};

Matrix<int> a;
Matrix<int> b = a.LoadFile<int>();

Beware of compatibility of this code, though, because these features have been only recently supported and some compilers don't do yet. You can see more about c++11 compiler support here .

If you could use the TypeLists from the ( http://www.amazon.com/Modern-Design-Generic-Programming-Patterns/dp/0201704315 ) - Loki you could implement something like:

template<bool>
struct Static_Assert;

template<>
struct Static_Assert<true>{};

class B{};

template<typename T>
class A{
public:
  A(){
    Static_Assert< 0 == utils::HasType<T, TYPELIST_2(B,int) >::value >();
  }
};

Then your HasType would be something like:

template<typename T, typename TList>
struct HasType{
  enum { value = 0+HasType< T, typename TList::Tail >::value };
};

template<typename T>
struct HasType< T, NullType >{
  enum { value = 0 };
};

template<typename T, typename U>
struct HasType< T, TypeList<T, U> >{
  enum { value = 1 };
};

In the list you can add the classes which you would like prevent to be passed as the template parameters.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM