检查具有给定签名的函数然后以不同方式编译

Checking function with given signature then compile differently

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

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:

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 和 classes 的构造函数接受一个 int 参数,它将使用默认实现编译,而对于构造函数不同的 T2 和 classes,它将使用虚函数编译并强制 subclass 用 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;

}

不使用任何第 3 方库是否可行?如果我们必须使用 macros/preprocessors.

也是可以接受的

如果必须有一个函数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...
    }
    
}

据我所知,我们需要 class 模板专业化。甚至 C++20 requires 子句都不能应用于 virtual 函数,所以我们唯一能做的就是对整个 class 进行更改。

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 移动到它自己的 class.[=20= 来避免用大量前期成本复制它]

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");
    }
};