【问题标题】:Checking function with given signature then compile differently检查具有给定签名的函数然后以不同方式编译
【发布时间】:2021-07-07 00:42:11
【问题描述】:

在模板类中,我们可以检查成员函数的签名为不同的子类定义不同的编译行为吗?更具体地说,请考虑以下简单示例:

template <typename T>
class Foo {
    // ingore all other functions

    virtual std::shared_ptr<T> do_something(int a) {
        return std::make_shared<T>(a);
    }
};

这应该适用于 T1 类:

class T1 {
    T1(int a) {
        // implememntation
    }

    // ingore all other functions
};

// Foo<T1> would just work

但是,使用T2 会导致编译失败,因为Foodo_something 函数被明确实现为仅使用一个参数调用T 的构造函数:

class T2 {
    T2(int a, int b) {
        // implememntation
    }

    // ingore all other functions
};

// Foo<T2> would fail to compile

所以问题是,我们能否对Foo 进行重新设计,让它同时适用于T1T2,就像T1 和构造函数采用int 参数的类一样,它将使用默认实现进行编译,而对于T2 和构造函数不同的类,它将使用虚函数编译并强制子类使用override 实现它。有点像下面:

Template <typename T>
class Foo {
    // ingore all other functions

    /* if T's signature takes one int input only
     * compile like the below
     */
    virtual std::shared_ptr<T> do_something(int x) {
        return std::make_shared<T>(x);
    }

    /* if T's signature is different
     * compile like the below and let the subclass implement it
     */
    virtual std::shared_ptr<T> do_something(int x) = 0;

}

这是否可能不使用任何 3rd-party 库?如果我们必须使用宏/预处理器,这是可以接受的。

如果必须有一个函数do_something 也是可以接受的,但是对于签名不匹配的情况,它只会引发运行时异常,例如:

Template <typename T>
class Foo {
    // ingore all other functions

    virtual std::shared_ptr<T> do_something(int x) {
        /* if T's signature takes one int input only
         * compile like the below
         */
        // return std::make_shared<T>(x);
        /* if T's signature is different
         * compile like the below and let the subclass implement it
         */
        // std::throws...
    }
    
}

【问题讨论】:

  • do_something 是虚拟的有特定原因吗?
  • 是的,因为子类可能希望在应用程序中覆盖它;和Foo 的其他函数可能也需要调用覆盖的do_something
  • 在 C++ 中不可能有两个具有相同签名的类方法,SFINAE 也无济于事。 Foo 本身有可能从基于 T 的专门化类继承,其专门化将实现适当的 do_something 替代方案。这将是一堆代码,但这是可能的,并且对于每个do_something 重载都需要做同样的事情。这是这里能做的最多的事情。
  • 为什么不简单地将Foo::do_something() 设为可变参数模板,以便它可以接受任意数量的构造函数参数?
  • 因为它必须是虚拟的,见上文。

标签: c++ templates macros


【解决方案1】:

据我所知,我们在这里需要类模板专业化。甚至 C++20 requires-clauses 也不能应用于 virtual 函数,所以我们唯一能做的就是改变整个类。

template<typename T> // using C++20 right now to avoid SFINAE madness
struct Foo {
    virtual ~Foo() = default;
    virtual std::shared_ptr<T> do_something(int a) = 0;
};
template<std::constructible_from<int> T>
struct Foo<T> {
    virtual ~Foo() = default; // to demonstrate the issue of having to duplicate the rest of the class
    virtual std::shared_ptr<T> do_something(int a) {
       return std::make_shared<T>(a);
    }
};

如果Foo 中有很多东西,您可以通过将do_something 移动到它自己的类来避免以大量前期成本重复它。

namespace detail { // this class should not be touched by users
    template<typename T>
    struct FooDoSomething {
        virtual ~FooDoSomething() = default;
        virtual std::shared_ptr<T> do_something(int a) = 0;
    };
    template<std::constructible_from<int> T>
    struct FooDoSomething<T> {
        virtual ~FooDoSomething() = default;
        virtual std::shared_ptr<T> do_something(int a);
    };
}
template<typename T>
struct Foo : detail::FooDoSomething<T> {
    // other stuff, not duplicated
    // just an example
    virtual int foo(int a) = 0;
};
namespace detail {
    template<std::constructible_from<int> T>
    std::shared_ptr<T> FooDoSomething<T>::do_something(int a) {
        Foo<T> &thiz = *static_cast<Foo<T>*>(this); // if you need "Foo things" in this default implementation, then FooDoSomething is *definitely* unsafe to expose to users!
        return std::make_shared<T>(thiz.foo(a));
    }
}

Godbolt

要将其从 C++20 转换下来,请将基于概念的专业化替换为旧类型的分支:

// e.g. for the simple solution
template<typename T, bool = std::is_constructible_v<T, int>>
struct Foo { // false case
    // etc.
};
template<typename T>
struct Foo<T, true> { // true case
    // etc.
};
// or do this to FooDoSomething if you choose to use that

运行时错误要容易得多,至少在 C++17 及更高版本中,因为您可以使用 if constexpr 来避免编译有问题的代码

template<typename T>
struct Foo {
    virtual ~Foo() = default;
    virtual std::shared_ptr<T> do_something(int a) {
        if constexpr(std::is_constructible_v<T, int>) return std::make_shared<T>(a);
        else throw std::logic_error("unimplemented Foo<T>::do_something");
    }
};

【讨论】:

  • 这是超级完整且易于理解的。非常感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 2014-06-14
  • 2011-05-11
  • 2011-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-16
相关资源
最近更新 更多