迫使非限定名称为依赖值 [英] forcing unqualified names to be dependent values

查看:133
本文介绍了迫使非限定名称为依赖值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在一些遗留的测试框架代码中,有一种模式取决于Visual C ++破碎的两阶段查找,导致移植到其他编译器时的头痛。有很多解决方案,我知道解决这个问题,但都需要广泛的结构性变化。



虽然我相当肯定没有,我好奇,如果可能有一个容易的黑客获得符合标准的编译器中所需的行为,并且需要一小组修改。



示例:

  #include< cstdio> 

//全局系统函数进行测试;通常在实际测试中像`fopen`一样
const char * GetString(){returnGLOBAL; }

//不提供对被测试的标准系统函数的重写
struct NoOverrides {};

//覆盖正在测试的系统函数的函数集
struct TestOverrides {
//如果这是fopen,这可能是一个总是失败的版本
static const char * GetString(){returnOVERRIDE; }
};

//测试用例
模板< typename覆盖>
struct Test:private覆盖{
void Run(){
//调用GetString不依赖于覆盖
printf(%s\\\
,GetString );
}
};

int main(){
//测试没有覆盖;使用系统函数
Test< NoOverrides> test1;
test1.Run();

//使用覆盖测试;使用测试用例版本的系统函数
Test< TestOverrides> test2;
test2.Run();
}

这个想法是有全局函数, (例如ANSI C功能或OS提供的功能)。然后有一个类型定义了一组替代版本的这个作为静态成员函数。测试可以从具有这些备用版本的类型继承,也可以从没有备选版本的类型继承。



使用Visual C ++破坏的两阶段查找,被测试的系统函数被延迟,直到模板实例化。如果覆盖类型 TestOverrides Test 类型的基本类型,则静态成员版本 GetString 。对于正确实现两阶段查找的其他编译器,在初始解析期间找到自由函数版本,并且在实例化模板的时候已经解决了自由函数版本。



知道一些相对干扰的解决这个问题的解决方案。一个是使 NoOverrides 类型实际上具有调用自由函数的包装器,然后调用 GetString 限定到覆盖模板参数,这是我的第一本能。示例:

  #include< cstdio> 

const char * GetString(){returnGLOBAL; }

//调用系统函数的版本
struct NoOverrides {
static const char * GetString(){return :: GetString(); }
};

struct TestOverrides {
static const char * GetString(){returnOVERRIDE; }
};

template< typename覆盖>
struct Test {
void Run(){
//调用GetString取决于模板类型覆盖
printf(%s\\\
,Overrides :: GetString ());
}
};

int main(){
//使用默认覆盖测试;使用系统函数
Test< NoOverrides> test1;
test1.Run();

Test< TestOverrides> test2;
test2.Run();
}

很明显,有工作的解决方案来处理两阶段查找。很多这些测试可能相当复杂,并且需要大量的工作来转换使用像我刚才提供的结构。我很好奇,如果有另一个解决方案,需要较少的结构变化的代码,我不在想。

解决方案

这是我最小的侵入版本没有SFINAE。用Apple LLVM 5.1和g ++ 4.8.2测试。这只是代码下面描述的一般想法的一个实现。

  #include< cstdio> 
//全局系统函数进行测试;通常在实际测试中像`fopen`一样
const char * GetString(){returnGLOBAL; }

//列出所有可能被函数指针覆盖的全局函数
//(用原始函数初始化它们)并提供模板覆盖函数。
struct PossibleOverridesList
{
decltype(& :: GetString)GetString =& :: GetString;

template< typename O>
void setOverrides(){
O :: setMyOverrides(this);
};

};

//不提供被测试的标准系统函数的覆盖
//(setOverrides方法不做任何操作)
struct NoOverrides {static void setMyOverrides(PossibleOverridesList * ol){}; };

//覆盖正在测试的系统函数的函数集
//(setOverrides方法设置指向静态成员函数的所需指针)
struct TestOverrides {
/ /如果这是`fopen`这可能是一个版本总是失败
static const char * GetString(){returnOVERRIDE; }
static void setMyOverrides(PossibleOverridesList * ol){ol-> GetString =& GetString; };
};

//测试用例(继承不依赖于模板参数,所以它包含在查找中)
struct Test:PossibleOverridesList {
void Run(){
printf(%s\\\
,GetString());
}
};

int main(){
//测试没有覆盖;使用系统函数
Test test1;
test1.setOverrides< NoOverrides>();
test1.Run();

//使用覆盖测试;使用测试用例版本的系统函数
Test test2;
test2.setOverrides< TestOverrides>();
test2.Run();
}

我的想法是下面这样:因为我的理解是你不想更改 Run()中的代码,没有办法从模板类中查找未修改的名称。因此,在查找 GetString 时,一些(可调用)占位符对象必须在作用域中,我们仍然可以决定调用哪个函数(如果 GetString ()里面运行一次绑定到全局 GetString()做任何事情以后改变它)。这可以是周围命名空间中的函数或(非模板)基类中的函数/函数对象/函数指针。我在这里选择后者(使用 PossibleOverridesList )以尽可能接近原始代码。默认情况下,此占位符调用函数的原始版本,但可以通过类似于覆盖类的类进行修改。唯一的区别是这些覆盖类需要额外的方法 setMyOverrides(PossibleOverridesList *)相应地设置占位符类中的函数指针。



main中的更改不是绝对必要的。在我的代码中,你必须调用 setOverrides< ... OverriderClass ...>(),而不是在声明中指定OverriderClass。但是应该很容易编写一个从Test继承的包装器模板类,并通过在构建过程中使用其自身的模板参数内部调用 setOverrides 方法来保留原始语法。 p>

上面的代码比在 NoOverrides 类中提供包装器的解决方案具有几个优点(你的建议是你的问题):




  • 对于包装器,如果你有许多覆盖类有许多重写的函数,你必须总是提供包装器, t想覆盖。这里,只有一个可能被重写的全局函数列表, PossibleOverridesList 。很容易发现,如果你忘记了一个,因为试图在相应的Override类的 setMyOverrides 方法中设置该成员将无法编译。因此,如果在任何覆盖类中添加另一个覆盖方法,只有两个地方可以更改。

  • 您不需要为系统函数的包装器重现函数签名

  • 您不必更改 Run()中的代码即可使用继承类中的函数。

  • 我不知道在你原来的复杂代码为VC ++测试能否实际继承自多个覆盖类。但是如果可以,包装解决方案将不工作,因为你不知道如何限定方法(你不知道从哪个继承类来)。在这里,您可以在Test对象上多次轻松调用 setOverrides()方法来连续替换某些函数。



当然也有局限性,例如,如果原来的系统函数不在 :: 命名空间,但也许他们不是更多严格比包装解决方案。正如我上面所说,可能有非常不同的实现使用相同的概念,但我认为有一些形式的默认占位符的重写方法是不可避免的(虽然我很想看到没有它的解决方案)。






编辑:
我刚刚想出了一个更少的侵入版本,只需要额外的 PossibleOverridesList 类,但没有更改 Run() main()最重要的是,不需要 setOverrides 方法和 Test< Override> 原始代码保留



这里的诀窍是使用虚拟方法而不是静态方法,并利用虚拟继承。这样,在 Run()中的 GetString()的函数调用可以绑定到 PossibleOverridesList (因为继承不依赖于模板参数)。然后在运行时,调用被分派到最导出的类,即在重载类中。这只是明确的,因为虚拟继承,所以,事实上,只有一个 PossibleOverridesList 对象存在于类中。



因此,总之,此版本的最小变化是:




  • define PossibleOverridesList
  • 更改重载类中的成员方法静态虚拟

  • 让所有覆盖和 Test 类实际上继承了PossibleOverridesList以及任何其他继承。 NoOverride 它不是绝对必要的,但对于一致的模式很好。



这里的代码(再次,用苹果LLVM 5.1和g ++ 4.8.2测试):

  #include< cstdio& 
//全局系统函数进行测试;通常在实际测试中像`fopen`一样
const char * GetString(){returnGLOBAL; }

//列出所有可能被函数指针覆盖的全局函数
struct PossibleOverridesList
{
virtual const char * GetString(){return :: GetString ();};
};

//不提供正在测试的标准系统函数的覆盖
struct NoOverrides:virtual PossibleOverridesList {};

//覆盖正在测试的系统函数的函数集
struct TestOverrides:virtual PossibleOverridesList {
//如果这是fopen,这可能是一个总是失败的版本
virtual const char * GetString(){returnOVERRIDE; }
};

//测试用例(从第一个类继承不依赖于模板参数,所以它包含在查找中)
template< typename Override>
struct Test:virtual PossibleOverridesList,Override {
void Run(){
printf(%s\\\
,GetString());
}
};

int main(){
//测试没有覆盖;使用系统函数
Test< NoOverrides> test1;
test1.Run();

//使用覆盖测试;使用测试用例版本的系统函数
Test< TestOverrides> test2;
test2.Run();
}


There's a pattern in some legacy test framework code that depends on Visual C++'s broken two-phase look up, causing headaches when porting to other compilers. There are numerous solutions that I know of to fix the issue but all require "extensive" structural changes.

While I'm fairly certain that there isn't, I am curious if there may be an "easy" hack that gets the desired behavior in standards-compliant compilers with a very small set of required changes.

The pattern is seen in this example:

#include <cstdio>

// global "system" function to test; generally something like `fopen` in a real test
const char* GetString() { return "GLOBAL"; }

// provides no overrides of the standard system functions being tested
struct NoOverrides {};

// set of functions overriding the system functions being tested
struct TestOverrides {
  // if this were `fopen` this might be a version that always fails
  static const char* GetString() { return "OVERRIDE"; }
};

// test case
template <typename Overrides>
struct Test : private Overrides {
  void Run() {
    // call to GetString is not dependent on Overrides
    printf("%s\n", GetString());
  }
};

int main() {
  // test with no overrides; use the system functions
  Test<NoOverrides> test1;
  test1.Run();

  // test with overrides; use test case version of system functions
  Test<TestOverrides> test2;
  test2.Run();
}

The idea is that there are global functions, usually something defined in a system header (such as an ANSI C function or OS-provided function). There is then a type that defines a bunch of alternate versions of this as static member functions. A test can inherit either from the type that has these alternate versions or a type that has no alternatives.

With Visual C++'s broken two-phase lookup, the unqualified calls to the system functions being tested are delayed until template instantiation. If the overrides type TestOverrides is a base type of the Test type, then the static member version of GetString is found. With other compilers that properly implement two-phase lookup, the free function version is found during initial parsing and is already resolved by the time the template is instantiated.

I'm well aware of some relatively intrusive solutions to this problem. One would be to make the NoOverrides type actually have wrappers that call the free functions and then make the call to GetString qualified to the Overrides template parameter, which is my first instinct. Example:

#include <cstdio>

const char* GetString() { return "GLOBAL"; }

// wrappers to invoke the system function versions
struct NoOverrides {
  static const char* GetString() { return ::GetString(); }
};

struct TestOverrides {
  static const char* GetString() { return "OVERRIDE"; }
};

template <typename Overrides>
struct Test {
  void Run() {
    // call to GetString is made dependent on template type Overrides
    printf("%s\n", Overrides::GetString());
  }
};

int main() {
  // test with the default overrides; use the system functions
  Test<NoOverrides> test1;
  test1.Run();

  Test<TestOverrides> test2;
  test2.Run();
}

Clearly there are working solutions to deal with two-phase lookup. A good number of these tests can be fairly complex and would take a lot of work to convert to use a structure like the one I just provided. I'm curious if there is another solution that requires less structural changes to the code that I'm not thinking of.

解决方案

Here's my least intrusive version without SFINAE. Tested with Apple LLVM 5.1 and g++ 4.8.2. This is only one realization of the general idea described below the code.

#include <cstdio>
// global "system" function to test; generally something like `fopen` in a real test
const char* GetString() { return "GLOBAL"; }

// list all global functions that could possibly be overridden by function pointers
// (initialize them with the original function) and provide template overriding function.
struct PossibleOverridesList
{
    decltype(&::GetString) GetString = &::GetString;

    template<typename O>
    void setOverrides() {
        O::setMyOverrides(this);
    };

};

// provides no overrides of the standard system functions being tested
// (setOverrides method does nothing)
struct NoOverrides { static void setMyOverrides(PossibleOverridesList* ol){}; };

// set of functions overriding the system functions being tested
// (setOverrides method sets the desired pointers to the static member functions)
struct TestOverrides {
  // if this were `fopen` this might be a version that always fails
  static const char* GetString() { return "OVERRIDE"; }
  static void setMyOverrides(PossibleOverridesList* ol) { ol->GetString = &GetString; };
};

// test case (inheritance doesn't depend on template parameters, so it gets included in the lookup)
struct Test : PossibleOverridesList {
  void Run() {
    printf("%s\n", GetString());
  }
};

int main() {
  // test with no overrides; use the system functions
  Test test1;
  test1.setOverrides<NoOverrides>();
  test1.Run();

  // test with overrides; use test case version of system functions
  Test test2;
  test2.setOverrides<TestOverrides>();
  test2.Run();
}

My idea is the following: Since my understanding is that you do not want to change the code inside Run(), there is no way to look up the unmodified name from a template class. Therefore, some (callable) placeholder object must be in scope when GetString is looked up, inside which we can still decide which function to call (if GetString() inside Run() binds to the global GetString() once, we cannot do anything later to change it). This could be a function in the surrounding namespace or a function/function object/function pointer in a (non-template) base class. I chose the latter here (with the class PossibleOverridesList) to stay as close to your original code as possible. This placeholder by default calls the original version of the function but can be modified by classes similar to your Override classes. The only difference is that those Override classes need the additional method setMyOverrides(PossibleOverridesList*) which sets the function pointers in the placeholder class accordingly.

The changes in main are not strictly necessary. In my code, you have to call setOverrides<...OverriderClass...>() instead of specifying the OverriderClass in the declaration. But it should be easy to write a wrapper template class that inherits from Test and preserves the original syntax by internally calling the setOverrides method with its template argument on itself during construction.

The code above has several advantages over the solution of providing wrappers in the NoOverrides class (your suggestion the your question):

  • With wrappers, if you have many Override classes with many overridden functions, you would have to always provide wrappers for all the functions you don't want to override. Here, there is just one global list of functions that might be overridden, PossibleOverridesList. It's easy to spot if you forgot one, because trying to set that member in the setMyOverrides method of the corresponding Override class will not compile. So there are only two places to change something if you add another overridden method in any Override class.
  • You do not need to reproduce the function signature for the wrapper of the system function
  • You do not have to change the code inside Run() to use the functions from the inherited class.
  • I don't know whether in your original complex code for VC++ Test can actually inherit from multiple Override classes. But if it can, the wrapping solution will not work since you don't know how to qualify the method (you don't know from which inherited class it comes). Here, you can easily call the setOverrides() method multiple times on the Test object to replace certain functions successively.

Of course there are also limitations, for example if the original system function is not in the :: namespace, but maybe they are not more strict than for the wrapper solution. As I said above, there are probably very different implementations that use the same concept, but I think having some form of default placeholder for the overridden methods is inevitable (although I would love to see a solution without it).


EDIT: I just came up with an even less intrusive version that only needs the additional PossibleOverridesList class, but no changes to Run(), main(). Most importantly, there is no need for the setOverrides methods and the Test<Override> templated inheritance from the original code is preserved!

The trick here is to use virtual methods instead of static ones and exploit virtual inheritance. This way, the function call to GetString() in Run() can bind to the virtual function in PossibleOverridesList (because that inheritance does not depend on the template parameter). Then at run time, the call is dispatched to the most derived class, i.e. in the override class. This is only unambiguous because of the virtual inheritance, so, in fact, only one PossibleOverridesList object is present in the class.

So, in summary, the minimal changes in this version are:

  • define PossibleOverridesList with all the functions that might be overridden (as virtual member functions), redirecting to the original system function.
  • change member methods in the override classes from static to virtual.
  • let all override and the Test classes inherit virtually from PossibleOverridesList in addition to any other inheritance. For NoOverride it is not strictly necessary, but nice for a consistent pattern.

Here's the code (again, tested with Apple LLVM 5.1 and g++ 4.8.2):

#include <cstdio>
// global "system" function to test; generally something like `fopen` in a real test
const char* GetString() { return "GLOBAL"; }

// list all global functions that could possibly be overridden by function pointers
struct PossibleOverridesList
{
    virtual const char* GetString() {return ::GetString();};
};

// provides no overrides of the standard system functions being tested
struct NoOverrides : virtual PossibleOverridesList { };

// set of functions overriding the system functions being tested
struct TestOverrides : virtual PossibleOverridesList {
  // if this were `fopen` this might be a version that always fails
  virtual const char* GetString() { return "OVERRIDE"; }
};

// test case (inheritance from first class doesn't depend on template parameter, so it gets included in the lookup)
template <typename Override>
struct Test : virtual PossibleOverridesList, Override {
  void Run() {
    printf("%s\n", GetString());
  }
};

int main() {
  // test with no overrides; use the system functions
  Test<NoOverrides> test1;
  test1.Run();

  // test with overrides; use test case version of system functions
  Test<TestOverrides> test2;
  test2.Run();
}

这篇关于迫使非限定名称为依赖值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆