且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

迫使非限定名称为依赖值

更新时间:2023-11-09 20:14:34

这是我最小的侵入版本没有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 $ c

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

  • 让所有覆盖和 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();
}