这是如何运作的? (C ++名称查找魔术)

时间:2017-06-24 13:30:37

标签: c++ templates argument-dependent-lookup

我一直在查看cerealboost::serialization代码,以了解课程注册的工作原理,但却无法理解。

我理解会发生什么:

  • 我请求明确实例化static_object<magic<B№>>
  • 在构造函数中调用adl_magic,这会导致编译器实例化其所有重载。
  • 因为某些重载的返回类型引用typename instantiator<inserter<A№, T>>::type,编译器会生成一个实例instantiator(没有双关语)。
  • 现在,我不明白接下来会发生什么。为什么它实例化static_object<nserter<A№, T>>,即使它是从一个从未调用的函数内部引用的?为什么需要dummy(为什么不同的编译器需要不同的编译器)?如果我将typename instantiator<inserter<A№, T>>::type替换为typename static_object<inserter<A№, T>>::type
  • ,为什么它不起作用

其余的代码对我来说似乎很明显。

template<typename T>
class static_object {
    static void use(T const&) {}

    static T& ref;
    static T& create()
    {
        static T object;
        use(ref);  // why it doesn't work without this line?
        return object;
    }

public:
    static T& instance() { return create(); }
};
template <class T>
T& static_object<T>::ref = static_object<T>::create();



template <void(*)()> struct instantiate_function {};

template<typename T>
struct instantiator {
    static void instantiate() { static_object<T>::instance(); }

#ifdef _MSC_VER
    virtual void dummy() { instantiate(); }
#else
    using dummy = instantiate_function<instantiate>;
#endif
};

#include <string>
#include <vector>
// This gets called when stuff below is instantiated
using list = std::pair<std::string, std::string>;
using list = static_object<std::vector<string_pair>>;
template<typename A, typename B>
struct inserter {
    inserter()
    {
        list::instance().push_back(std::pair{A::name, B::name});
    }
};

// These are just some structs for demonstration.
struct A1 { static const char name[]; }; const char A1::name[] = "A1";
struct A2 { static const char name[]; }; const char A2::name[] = "A2";
struct B1 { static const char name[]; }; const char B1::name[] = "B1";
struct B2 { static const char name[]; }; const char B2::name[] = "B2";
struct B3 { static const char name[]; }; const char B3::name[] = "B3";

// I've omitted an "adl_tag" argument, which is needed to make 
// sure ADL finds all overloads
template<typename T> void adl_magic(T*, int) {}

// each of these would be behind some REGISTER_ARCHIVE(A) macro
template<typename T> typename instantiator<inserter<A1, T>>::type adl_magic(T*, A1*);
template<typename T> typename instantiator<inserter<A2, T>>::type adl_magic(T*, A2*);

template<typename T>
struct magic {
    magic()
    {
        adl_magic(static_cast<T*>(nullptr), 0);
    }
};

// each of these would be behind some REGISTER_CLASS(B) macro
template struct static_object<magic<B1>>;
template struct static_object<magic<B2>>;
template struct static_object<magic<B3>>;

#include <iostream>
int main()
{
    for(auto& p : list::instance())
        std::cout << p.first << ' ' << p.second << '\n';
}

编辑:如果我用这些声明改变上面的相应声明,它们似乎适用于所有编译器。我不明白他们为什么会这样做,但我认为他们这样做是因为auto迫使static_object<T>实例化推断出类型。

template<typename T>
struct instantiator {
    static auto instantiate() { return static_object<T>::instance(); }
};

template<typename T> decltype(instantiator<inserter<A1, T>>::instantiate()) adl_magic(T*, A1*);
template<typename T> decltype(instantiator<inserter<A2, T>>::instantiate()) adl_magic(T*, A2*);

还有另一种变体,它只适用于GCC,而不适用于其他编译器:

template<typename T>
struct instantiator {
    static T& ref;
};
template<typename T>
T& instantiator<T>::ref = static_object<T>::instance();

template<typename T> decltype(instantiator<inserter<A1, T>>::ref) adl_magic(T*, A1*);
template<typename T> decltype(instantiator<inserter<A2, T>>::ref) adl_magic(T*, A2*);

1 个答案:

答案 0 :(得分:0)

  

现在,我不明白接下来会发生什么。为什么它实例化static_object&gt;,即使它是从一个从未调用的函数内部引用的?

实例化的发生是因为::type需要查看该类模板特化,因此必须实例化它。

  

为什么需要虚拟(为什么不同的编译器需要不同的编译器)?

无法回答带括号的部分,但dummy引用instantiate,而没有其他内容。有些东西必须引用instantiate,以便编译器编译其中的代码。即使未使用,也始终实例化类模板的虚拟成员函数,这样才能实现。 using别名将函数的地址传递给另一个模板,该模板触发编译器编译instantiate

  

如果我用typename static_object&gt; :: type?替换typename instantiator&gt; :: type,它为什么不起作用?

因为那时你已经实例化了static_object<T>,但是它只实例化了成员函数的声明(例如虚拟成员函数或使用别名)和静态数据成员声明等。不是成员职能机构。因此,它不会触发static_object<...>的静态数据成员定义的实例化,因此它不会创建T的对象,因此它不会扩展list

 use(ref);  // why it doesn't work without this line? 

由于instantiate引用static_object<T>::instance引用static_object<T>::create引用ref,因此将其传递给use。如果删除了最后一件事,则不再需要ref的存在,因此其定义将不会被实例化。