基于Abstract Factory中可用的重载版本自动选择构造函数

时间:2017-10-16 03:46:33

标签: c++ templates overloading

我正在使用C ++模板编写一个抽象工厂,并遇到了一个小障碍。即,泛型类T可以提供以下一种或多种方式来构造对象:

static T* T::create(int arg);
T(int arg);
T();

我正在编写抽象工厂类,以便它可以按给定的顺序自动尝试这三种潜在的构造:

template <class T>
class Factory {
public:
    T* create(int arg) {
        return T::create(arg);  // first preference
        return new T(arg);  // this if above does not exist
        return new T;  // this if above does not exist
        // compiler error if none of the three is provided by class T
    }
};

如何使用C ++模板实现此目的?谢谢。

2 个答案:

答案 0 :(得分:5)

沿着这条线的东西应该有效:

struct S { static auto create(int) { return new S; } };
struct T { T(int) {} };
struct U {};

template<int N> struct tag: tag<N-1> {};
template<> struct tag<0> {};

class Factory {
    template<typename C>
    auto create(tag<2>, int N) -> decltype(C::create(N)) {
        return C::create(N);
    }

    template<typename C>
    auto create(tag<1>, int N) -> decltype(new C{N}) {
        return new C{N};
    }

    template<typename C>
    auto create(tag<0>, ...) {
        return new C{};
    }

public:
    template<typename C>
    auto create(int N) {
        return create<C>(tag<2>{}, N);
    }
};

int main() {
    Factory factory;
    factory.create<S>(0);
    factory.create<T>(0);
    factory.create<U>(0);
}

它基于sfinae和标签调度技术 基本思想是将工厂的create函数转发给一组内部函数。由于存在tag,这些函数会按顺序测试您要查找的功能,如果测试失败则会丢弃。由于sfinae,只要其中一个成功,代码就会编译,一切都按预期工作。

这是C ++ 17中的类似解决方案:

#include <type_traits>
#include <iostream>
#include <utility>

struct S { static auto create(int) { return new S; } };
struct T { T(int) {} };
struct U {};

template<typename C> constexpr auto has_create(int) -> decltype(C::create(std::declval<int>()), bool{}) { return true; }
template<typename C> constexpr auto has_create(char) { return false; }

struct Factory {
    template<typename C>
    auto create(int N) {
        if constexpr(has_create<C>(0)) {
            std::cout << "has create" << std::endl;
            return C::create(N);
        } else if constexpr(std::is_constructible_v<C, int>) {
            std::cout << "has proper constructor" << std::endl;
            return new C{N};
        } else {
            std::cout << "well, do it and shut up" << std::endl;
            (void)N;
            return C{};
        }
    }
};

int main() {
    Factory factory;
    factory.create<S>(0);
    factory.create<T>(0);
    factory.create<U>(0);
}

感谢@StoryTeller和@ Jarod42在这个艰难的早晨提供帮助 在wandbox上查看并运行。

答案 1 :(得分:0)

好的,感谢@skypjack的the answer我能够提出一个更兼容的解决方案,适用于pre c ++ 11编译器。核心思想是相同的,即使用标签调度进行有序测试。我没有依赖于decltype,而是使用了sizeof和SFINAE的虚拟类。

struct S { static auto create(int) { return new S; } };
struct T { T(int) {} };
struct U {};

template<class C, int=sizeof(C::create(0))> struct test_1 { typedef int type; };
template<class C, int=sizeof(C(0))> struct test_2 { typedef int type; };
template<class C, int=sizeof(C())> struct test_3 { typedef int type; };

template<int N> struct priority: priority<N-1> {};
template<> struct priority<0> {};

class Factory {
    template<typename C>
    C* create(priority<2>, typename test_1<C>::type N) {
        return C::create(N);
    }

    template<typename C>
    C* create(priority<1>, typename test_2<C>::type N) {
        return new C(N);
    }

    template<typename C>
    C* create(priority<0>, typename test_3<C>::type N) {
        return new C();
    }

public:
    template<typename C>
    C* create(int N) {
        return create<C>(priority<2>(), N);
    }
};

int main() {
    Factory factory;
    factory.create<S>(0);
    factory.create<T>(0);
    factory.create<U>(0);
}

不确定是否可以将sizeof部分填充到私有函数签名中;如果是这样,我们也可以摆脱虚拟类。(失败)稍微丑陋的部分是使用0运算符的常量(在这种情况下为sizeof),这可能会得到如果构造函数采用非常复杂类型的参数,那就太棘手了。