使用SFINAE的C ++ 11模板函数特化允许进一步的特化(错误:重载函数的调用是不明确的)

时间:2017-06-10 00:08:31

标签: c++ templates metaprogramming sfinae

这是我想要完成的事情(请参阅评论)以及我到目前为止所做的事情。
此代码可编译且可运行 - ideone link

#include <type_traits>
#include <memory>
#include <iostream>

template<class T>
class ObjectMustBeCreatedType : public std::false_type {
  // needed for proper static_assert<T> below
};

// default function that has to be specialized, otherwise compiler error
template<class T>
std::shared_ptr<T> CreateObject(const std::string &path) {
    static_assert(ObjectMustBeCreatedType<T>::value, 
                  "please specialize this for your class");
}

// SFINAE to detect static T::Create function
template <
    typename T,
    typename = typename std::enable_if<
        std::is_same<
            std::shared_ptr<T>,
            decltype(T::Create(std::string{}))
        >::value
    >::type
>
std::shared_ptr<T> CreateObject(const std::string &s) {
    return T::Create(s); // if T::Create is found, call it
}

// for this class the SFINAE version should be triggered 
// and CreateObject<AutomaticClass> should be instantiated automatically
struct AutomaticClass {
    static std::shared_ptr<AutomaticClass> Create(const std::string &s) {
        std::cout << "AutomaticClass::Create" << std::endl;
        return std::make_shared<AutomaticClass>();
    }
};

// for this class CreateObject is manually specialized below
struct ManualClass {
    ManualClass(const std::string &s) { 
      std::cout << "ManualClass constructor: " << s << std::endl;
    }
};

// manual CreateObject<ManualClass> specialization
template<>
std::shared_ptr<ManualClass> CreateObject(const std::string &s) {
    std::cout << "CreateObject<ManualClass>" << std::endl;
    return std::make_shared<ManualClass>(s);
}

int main() {
    // this works
    CreateObject<ManualClass>("ManualClass test");

    // produces compile errors
    CreateObject<AutomaticClass>("AutomaticClass test");

    return 0;
}

现在,问题是对于SFINAE有效的情况,现在有两个函数都匹配模板,从而产生以下错误:

prog.cpp: In function ‘int main()’:
prog.cpp:59:59: error: call of overloaded ‘CreateObject(const char [20])’ is ambiguous
         CreateObject<AutomaticClass>("AutomaticClass test");
                                                           ^
prog.cpp:12:24: note: candidate: std::shared_ptr<_Tp1> CreateObject(const string&) [with T = AutomaticClass; std::__cxx11::string = std::__cxx11::basic_string<char>]
     std::shared_ptr<T> CreateObject(const std::string &path) {
                        ^~~~~~~~~~~~
prog.cpp:27:24: note: candidate: std::shared_ptr<_Tp1> CreateObject(const string&) [with T = AutomaticClass; <template-parameter-1-2> = void; std::__cxx11::string = std::__cxx11::basic_string<char>]
     std::shared_ptr<T> CreateObject(const std::string &s) {
                        ^~~~~~~~~~~~

如何通过以下方式解决这个问题:

  1. CreateObject<T>保持不变,因此进一步专业化 用户类看起来尽可能干净。
  2. 因此,CreateObject<T>不能移动到类以允许部分模板专业化,这更容易做但看起来 脏。
  3. 保留默认static_assert错误消息,以便用户清楚地看到他需要为他的班级专门化CreateObject<T>
  4. 使用C ++ 11标准,而不是更高版本。

2 个答案:

答案 0 :(得分:1)

这里的关键是你需要从一个功能模板切换到另一个功能模板,而不仅仅是启用/禁用两个功能模板中的一个。在后者中,您会混淆编译器的歧义。我认为,旧的member detector应该做好工作。

template <typename T>
struct has_static_member_create {
  template <typename U, std::shared_ptr<U> (*)(std::string const&)>
  struct Check;

  template <typename U>
  static std::true_type foo(Check<U, &U::Create>*);

  template <typename U>
  static std::false_type foo(...);

  constexpr static bool value = decltype(foo<T>(0))::value;
};

// More C++-11 style
// template <typename T, typename Enabled = void>
// struct has_static_member_create : std::false_type {};

// template <typename T>
// struct has_static_member_create<T,
//                                 typename std::
//                                   enable_if<std::is_same<decltype(&T::Create),
//                                                          std::shared_ptr<T> (*)(
//                                                            std::string const&)>::value>::
//                                     type> : std::true_type {};


// default function that has to be specialized, otherwise compiler error
template <typename T>
typename std::enable_if<!has_static_member_create<T>::value, std::shared_ptr<T>>::type
CreateObject(const std::string& path) {
  static_assert(ObjectMustBeCreatedType<T>::value,
                "please specialize this for your class");
}

// SFINAE to detect static T::Create function
template <typename T,
          typename = typename std::enable_if<has_static_member_create<T>::value>::type>
std::shared_ptr<T> CreateObject(const std::string& s) {
  return T::Create(s); // if T::Create is found, call it
}

// for this class the SFINAE version should be triggered
// and CreateObject<AutomaticClass> should be instantiated automatically
struct AutomaticClass {
  static std::shared_ptr<AutomaticClass> Create(const std::string& s) {
    std::cout << "AutomaticClass::Create" << std::endl;
    return std::make_shared<AutomaticClass>();
  }
};

// for this class CreateObject is manually specialized below
struct ManualClass {
  ManualClass(const std::string& s) {
    std::cout << "ManualClass constructor: " << s << std::endl;
  }
};

// manual CreateObject<ManualClass> specialization
template <>
std::shared_ptr<ManualClass> CreateObject<ManualClass>(const std::string& s) {
  std::cout << "CreateObject<ManualClass>" << std::endl;
  return std::make_shared<ManualClass>(s);
}

int main() {
  // this works
  CreateObject<ManualClass>("ManualClass test");

  // produces compile errors
  CreateObject<AutomaticClass>("AutomaticClass test");

  cerr << has_static_member_create<AutomaticClass>::value << endl;
  cerr << has_static_member_create<ManualClass>::value << endl;
  return 0;
}

随意使用探测器的其他实现和/或不同的功能模板启用/禁用技术。我展示的只是其中一种可行的方法。

答案 1 :(得分:1)

只需使用标签调度并使用所有特定的废话停止它。

Create<T>(s)执行return Create( tag<T>, s )。现在我们编写自动的:

template<class T>
std::shared_ptr<T> CreateObject(tag_t<T>, const std::string &s) {
  static_assert(std::is_same<std::shared_ptr<T>,decltype(T::Create(std::string{}))>::value,
    "Please provide T::Create(string) or override `Create(tag_t<T>, string)` for your type"
  );
  return T::Create(s); // if T::Create is found, call it
}

::Create不存在时,您可以使用检测到的惯用语来改进静态断言,以获得更清晰的错误。

现在,不要专门设置CreateObject,而是覆盖它:

std::shared_ptr<ManualClass> CreateObject(tag_t<ManualClass>, const std::string& s) {
  std::cout << "CreateObject<ManualClass>" << std::endl;
  return std::make_shared<ManualClass>(s);
}

最重要的是,此覆盖可以存在于ManualClass tag_t的命名空间中。