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

Choosing at compile time which function to call using templates

我正在使用 C++11,并试图在我的应用程序中设置一个泛型 Handle class,有时可以转换具有不同底层类型的句柄,但仅如果基础类型相关为 ancestor/descendant,否则尝试转换应该会失败。我还需要一个永远不会失败的函数,告诉我是否可以在两种类型之间进行转换。特别是,我不希望底层类型尝试对不在它自己的 ancestry/descendant 行内的类型进行任何转换,所以我在想我是否在编译时告诉我的布尔值上定义了一个模板仿函数类型是否相关,如果它们不相关,则使用模板特化来拒绝转换,如果它们相关,则将转换请求转发给基础类型。每个基 class 包含一个模板化的转换函数,它知道如何转换为它层次结构中的每个相应类型,以及一个模板化的布尔函数,该函数指示是否可以基于 [=85 的内部状态进行这种转换=]实例.

我放在一起的是这样的:

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 层次结构:

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>() 远离 returns false,例如。虽然 MyHandle<B>::as<C>() 总是会抛出异常,但甚至不会尝试生成对 B::as<C> 的调用,因为这可能会导致编译错误。

编辑:

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

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 的空指针,它可以包含指向有关实际基础类型的更多信息的指针,如果它是 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;    
};

我没有像以前那样抛出异常,而是 return 一个无效的 MyHandle(它有一个特殊的构造函数,MyHandle(nullptr_t),并且可以查询 MyHandle 的状态通过一个简单的布尔 is_valid() 方法,(如果需要的话,调用者可以选择抛出一个异常,就我的目的而言,与 as<U> 函数在失败时抛出异常。

MyHandle class 有一个模板化的 underlying_can_be 方法和模板化的 underlying_as 方法,它们只是将他们的请求转发给底层 class 类型的 can_beas 方法,分别。值得注意的是,如果不通过 MyHandleConverter<T,U,true> class 调用这些方法,编译器甚至不会生成这些方法,所以现在 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,则 return 为真或 has 方法不会导致编译器错误,或者在某些情况下无法在编译时检测到逻辑错误,甚至在 运行 时如果不在每个基础类型中编写复杂的检测逻辑 ascan_be 方法,只需检测这两个 class 都是从适当的类型派生的,以便转换过程看似成功。

这样,当are_handle_types_related检测到类型不兼容时,调用相应类型的can_beas方法将无效,[=的实例创建的 48=] 是 MyHandleConverter<T,U,false>,它不会尝试调用基础 class 类型,而 MyHandleConverter<T,U,true> 会调用,但只会在 classes 的地方实例化无论如何,已经发现调用基础类型的适当转换函数是可以接受的。

要专门化模板,您必须在专门化之前添加 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>();
    }
};

但是这也不能编译。根据 http://en.cppreference.com/w/cpp/language/template_specialization :

Member or a member template may be nested within many enclosing class templates. In an explicit specialization for such a member, there's a template<> for every enclosing class template that is explicitly specialized. In such a nested declaration, some of the levels may remain unspecialized (except that it can't specialize a class member template if its enclosing class is unspecialized)

所以我们不能完全特化模板而不特化 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>()U 参数
class A {
public:
  template<class U> bool can_be(const U*)
{
return can_be<U>();
}
  template<class U> MyHandle<U> as(const U*)
{
return as<U>();
}

  template<class U> bool can_be();
  template<class U> MyHandle<U> as();
};

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

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