在编译时选择使用模板调用哪个函数

时间:2017-04-08 06:08:59

标签: c++ c++11 templates template-specialization

我正在使用C ++ 11,并试图在我的应用程序中设置一个通用的Handle类,有时可以转换具有不同底层类型的句柄,但前提是基础类型是相关的作为祖先/后代,否则尝试转换应该只是失败。我还需要一个永不失败的函数,它告诉我两种类型之间是否可以进行转换。特别是,我不希望底层类型尝试对不在其自己的祖先/后代行中的类型进行任何转换,所以我在想是否在布尔上定义了一个模板化的函子,它在编译时告诉我是否类型是相关的,并且如果它们不相关则使用模板特化来拒绝转换,或者如果它们是相关的,则将转换请求转发到基础类型。每个基类都包含一个模板化的转换函数,它知道如何转换为它层次结构中的每个相应类型,以及一个模板化的布尔函数,它根据类实例的内部状态指示是否可以进行这种转换。

我放在一起的样子如下:

template<class T>
class MyHandle {
public:
    ...
    template<bool> struct can_be_ref {    
        template<class U> bool operator()(const MyHandle *, const U*) const
        {
        }
    };

    template<bool> struct as_ref {
        template<class U> MyHandle<U> operator()(const MyHandle *, const U*) const
        {
            throw std::runtime_error("Illegal type conversion");
        }
    };
    template<class U> bool can_be();
    template<class U> MyHandle<U> as();
private:
    const T* get_member_reference() const;
};

template<class T> struct MyHandle<T>::can_be_ref<true> {    
    template<class U> bool operator()(const MyHandle<T> *ptr, const U*)
    {
        ptr->get_member_reference()->can_be<U>();
    }
};

template<class T> struct MyHandle<T>::as_ref<true> {    
    template<class U> MyHandle<U> operator()(const MyHandle<T> *ptr, const U*) const
    {
        return ptr->get_member_reference()->as<U>();
    }
};

template<class T> template<class U> bool MyHandle<T>::can_be()
{
    return can_be_ref < std::is_base_of<T, U>::value || std::is_base_of<U, T>::value > ()(this, reinterpret_cast<const U *> (nullptr));
}

template<class T> template<class U> MyHandle<U> MyHandle<T>::as()
{
    return as_ref < std::is_base_of<T, U>::value || std::is_base_of<U, T>::value > ()(this, reinterpret_cast<const U *> (nullptr));
}

然而,这并没有编译,我也不知道我做错了什么。失败发生在我尝试专门化can_be_refas_ref结构的地方,编译器抱怨模板参数列表太少。

希望我想要做的是在我提供的解释和令人遗憾的代码片段之间清楚地说明不起作用,但这是我能想到描述我想要的唯一方法。做。我做错了什么?

编辑:

澄清,说我有以下类层次结构:

class A {
public:
  template<class U> bool can_be();
  template<class U> MyHandle<U> as();
...
};

class B : public A{
...
};

class C {
public:
  template<class U> bool can_be();
  template<class U> MyHandle<U> as();
...
};

每个层次结构都定义了can_beas方法,只关注其层次结构中的项目,特别是如果模板的参数是,则在某些情况下可能会导致编译器错误不是正确的类型,这就是必须在编译时检查类型的原因 并假设我们定义了以下变量:

MyHandle<A> a;
MyHandle<B> b;
MyHandle<C> c;

由于ab是相关类型,A::can_beA::as可以在它们之间自由使用,但A :: can_be可能会产生编译错误。因此,MyHandle中它们周围的包装器会隐藏它,以便MyHandle<A>::can_be<C>() aways返回false。虽然MyHandle<B>::as<C>()总是会抛出异常,甚至不会尝试生成对B::as<C>的调用,因为这可能会导致编译错误。

编辑:

Per Kamil的建议如下,解决方案是将模板定义迁移到周围的类。我所做的是创建一个帮助模板,如下所示:

template<class T,class U,bool> class MyHandleConverter
{
public:
    inline MyHandleConverter(const MyHandle<T> *) { }
    inline bool can_be() const { return false; }
    inline MyHandle<U> as() const { return MyHandle<U>(nullptr); }
};

我决定放弃在无效转换上抛出异常,现在MyHandle的每个实例都包含一个名为value的void指针,它可以包含指向实际底层类型的更多信息的指针,如果它无效则为nullptr ,然后我可以为MyHandleConverterClass创建一个部分特化,如下所示:

template<class T,class U> class MyHandleConverter<T,U,true> {
public:
    inline MyHandleConverter(const MyHandle<T> *ref):reference(ref) { }    
    inline bool can_be() const {
        if (std::is_base_of<T,U>::value) {
            return true;
        } else if (reference->value == nullptr) {
            return false;
        } else {
            return reference->underlying_can_be((const U*)(nullptr));
        }
    }
    inline MyHandle<U> as() const { 
        if (std::is_base_of<U,T>::value) {
            return MyHandle<U>(reference->value);
        } else if (reference->value == nullptr) {
            return MyHandle<U>(nullptr);
        } else {
            return reference->underlying_as((const U*)(nullptr)); 
        }
    }
private:
    const MyHandle<T> *reference;    
};

我没有像以前那样抛出异常,而是返回一个无效的MyHandle(它有一个特殊的构造函数MyHandle(nullptr_t),而MyHandle的状态可以通过一个简单的查询来查询boolean is_valid()方法,(如果需要的话,调用者可选择抛出一个异常,为了我的目的,我必须编写更少的try .... catch块,而不是as<U>函数在失败时抛出异常)。

MyHandle类有一个模板underlying_can_be方法和模板化underlying_as方法,只需将其请求转发给基础类类型的can_beas方法, 分别。值得注意的是,如果没有通过MyHandleConverter<T,U,true>类调用这些方法甚至不会由编译器生成,那么现在MyHandle can_beas方法是这样写的:

template <class T> template<class U> bool MyHandle<T>::can_be() const { 
    return MyHandleConverter<T, U, are_related_handle_types<U,T>()>(this).can_be(); 
}

template<class T> template<class U> MyHandle<U> MyHandle<T>::as() const { 
    return MyHandleConverter<T, U, are_handle_types_related<U,T>()>(this).as(); 
}

其中are_handle_types_related是一个模板化的constexpr函数,如果发现调用基础模板化类型与调用MyHandle的基础类型can_be或{{ 1}}方法不会导致编译器错误,或者在某些情况下会导致无法在编译时检测到的逻辑错误,甚至在运行时也不会在每个基础类型has和{{1}中编写复杂的检测逻辑方法,只需检测这两个类是否都是从适当的类型派生出来,以便转换过程合理地成功。

这样,当as检测到的类型不兼容,并且调用相应类型的can_beare_handle_types_related方法无效时, can_be创建的as不会尝试调用基础类类型,而MyHandleConverter会这样做,但只会针对已经发现可以接受调用的类进行实例化无论如何,基础类型的适当转换功能。

1 个答案:

答案 0 :(得分:2)

要专门化模板,您必须在专业化之前添加template关键字,例如:

template<class T> // Template parameter for 'MyHandle<T>'
template<> // No unspecialized template parameters for 'can_be_ref', but indicate that it is a template anyway
struct MyHandle<T>::can_be_ref<true> 
{    
    template<class U> bool operator()(const MyHandle<T> *ptr, const U*)
    {
        ptr->get_member_reference()->can_be<U>();
    }
};

然而,这也无法编译。根据{{​​3}}:

  

成员或成员模板可以嵌套在许多封闭类中   模板。在对这样一个成员的明确专业化中,有一个   模板&LT;&GT;对于显式的每个封闭类模板   专门。在这样的嵌套声明中,某些级别可能   保持非专业化(除了它不能专门化一个类成员   模板,如果它的封闭类是非专业化的

因此,如果没有专门化MyHandle,我们也无法完全专门化模板。解决方案可能是模板参数的部分特化 - 将参数Ucan_be_ref::operator()移至can_be_ref级别:

template<class T>
class MyHandle
{
public:
...
    template<class U, bool>
    struct can_be_ref
    {
        bool operator()(const MyHandle<T> *ptr, const U*) const
        {
            return false;
        }
    };

    template<class U, bool>
    struct as_ref
    {
        MyHandle<U> operator()(const MyHandle<T> *, const U*) const
        {
            throw std::runtime_error("Illegal type conversion");
        }
    };
...
};

然后我们可以进行部分专业化:

template<class T>
template<class U>
struct MyHandle<T>::can_be_ref<U, true>
{
    bool operator()(const MyHandle<T> * ptr, const U*) const
    {
        return ptr->get_member_reference()->can_be<U>();
    }
};

template<class T>
template<class U>
struct MyHandle<T>::as_ref<U, true>
{
    MyHandle<U> operator()(const MyHandle<T> *ptr, const U*) const
    {
        return ptr->get_member_reference()->as<U>();
    }
};

template<class T>
template<class U> bool MyHandle<T>::can_be() const
{
    return can_be_ref<U,
            std::is_base_of<T, U>::value || std::is_base_of<U, T>::value>()(
            this, nullptr);
}

template<class T>
template<class U> MyHandle<U> MyHandle<T>::as()
{
    return as_ref<U,
            std::is_base_of<T, U>::value || std::is_base_of<U, T>::value>()(
            this, nullptr);
}

实际上,当我按照ABC符合要求时,在return ptr->get_member_reference()->can_be<U>();行上投诉:expected primary-expression before ')' token。我真的不明白这里有什么问题。像get_member_reference()->A::can_be<U>()一样调用它。有效的解决方法是通过传递U类型的参数来确定can_be<U>()的{​​{1}}参数:

U