如何解决为什么或何时将模板引入其命名空间?
这是一个例子:
#define MAKE_IT_WORK false
namespace Bob { // Bob's project namespace
struct DeviceFrequency {};
extern void debugf(const char* fmt,...);
} // namespace Bob
namespace SSN { // Super-Secret Namespace
namespace debugging {
extern int ssn_debug;
extern void debugf(const char* fmt,...);
} // namespace debugging
} // namespace SSN
namespace SSN::signals { // Super-Secret Namespace,project signals
template<typename Coder> // In the example,this imports Bob's namespace
class Frequency {
public:
Frequency( void )
{ using namespace ::SSN::debugging; // Why isn't this enough??
using ::SSN::debugging::debugf; // Or this??
if( ssn_debug )
#if MAKE_IT_WORK
::SSN::debugging:: // How can a developer predict that this is needed??
#endif
debugf("Frequency(%p,%zd)::Frequency\n",this,sizeof(*this));
}
}; // class Frequency
} // namespace SSN::signals
struct Controller {
SSN::signals::Frequency<Bob::DeviceFrequency> bobcon;
Controller( void ) : bobcon() {}
}; // class Controller
在此示例中,Bob复制了debugf
函数,因为他不想将整个SSN名称空间带入自己的私有名称空间,并且不想每次使用它时都被完全限定使用它它。
SSN开发人员没有意识到模板也可以导入其名称空间(直到它发生),显然使ADL查找发挥作用。尽管ADL查找指出在名称空间中使用using语句会被忽略,但是为什么它完全起作用没有意义。对于一个名称空间无意识地污染另一个名称空间来说似乎太容易了,而预测内联代码中哪一天可能发生的地方太难了。
看起来(至少)在命名空间中使用模板时,每个命名空间函数引用必须完全合格,因为使用模板时,您无法预测何时可能发生名称冲突。这个对吗?如果是这样,是否有任何方法可以避免似乎需要输入所有多余的名称限定符?这次曝光是否仅限于模板,还是所有导入的内联代码都同样容易受到攻击?
使用gcc版本10.2.0和10.2.1(Red Hat 10.2.1-5)编译(Dirty.cpp),我收到以下消息:
make dirty c++ -o Dirty.o -c S/Dirty.cpp -D_CC_GCC -D_OS_BSD -D_HW_X86 -D_OS_LINUX -IS -IH -g -O3 -finline->functions -std=gnu++17 -Wall -Wextra -Wmissing-declarations -Wswitch-default -Werror S/Dirty.cpp: In instantiation of ‘SSN::signals::Frequency<Coder>::Frequency() [with Coder = Bob::DeviceFrequency]’: S/Dirty.cpp:146:32: required from here S/Dirty.cpp:139:12: error: call of overloaded ‘debugf(const char [30],SSN::signals::Frequency<Bob::DeviceFrequency>*,long unsigned int)’ is ambiguous 139 | debugf("Frequency(%p,sizeof(*this)); | ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ S/Dirty.cpp:124:13: note: candidate: ‘void SSN::debugging::debugf(const char*,...)’ 124 | extern void debugf(const char* fmt,...); | ^~~~~~ S/Dirty.cpp:117:13: note: candidate: ‘void Bob::debugf(const char*,...)’ 117 | extern void debugf(const char* fmt,...); |
(编辑:该示例已还原为原始格式,现在已由@ 1201ProgramAlarm重新格式化。出现一个修改后的版本,可以在下面的部分答案中测试示例。)
解决方法
对于模板,ADL包括与该模板关联的名称空间和实体
为模板类型参数提供的模板参数的类型。由于对debugf
的调用将this
作为参数,因此ADL将包含namespace Bob
,因为模板是用Bob::DeviceFrequency
实例化的。
更新示例:
// Last change: 2020/10/30 11:15 EDT
#define MAKE_IT_WORK true // Modified (Example compiles)
namespace Bob { // Bob's project namespace
extern double thing; // (Added for answer testing)
struct Bar { int foo; }; // (Added for answer testing)
struct Foo { int bar; }; // (Added for answer testing)
extern Foo bar; // (Added for answer testing)
struct DeviceFrequency { double f; }; // (Modified: added "double f;")
extern void debugf(const char* fmt,...);
static inline void function(DeviceFrequency& E) // (??TRICKY inference??)
{ debugf("default function,E: %f\n",E); }
} // namespace Bob
namespace SSN { // Super-Secret Namespace
namespace debugging {
extern int ssn_debug;
extern void debugf(const char* fmt,...);
} // namespace debugging
} // namespace SSN
namespace SSN::signals { // Super-Secret Namespace,project signals
template<typename Coder>
class Frequency {
// Note: The Function type references the template indirectly,and has
// no purpose other than to make sure that a <Coder> function handler
// receives a <Coder&> parameter. ADL doesn't care. It brings in the
// Coder's namespace looking for functions to override.
typedef std::function<void(Coder&)> Function; // (Added) Another "gotcha"
const Function function; // This indirectly references Coder&,thus Bob
double thing= 543.21; // (Added) Duplicates name in namespace Bob
double f= 123.45; // (Added) Duplicates name in Bob::DeviceFrequency
Bob::Foo bar{876}; // (Added) Uses Bob namespace
struct Bar { int foo= 0x0F00; } foo; // (Added) Duplicates a Bob struct name
struct Derived : public Bob::Foo {}; // (Added) Uses Bob namespace
Derived secret{{671}}; // (Added) Uses Bob namespace in derived object
struct Container { int n= 888; Bob::Bar m{999}; }; // (Added) Uses Bob namespace
Container content; // (Added) Uses Bob namespace in container object
public:
// This constructor added to demonstrate indirect template references
Frequency( const Function& _function ) : function(_function)
{ using namespace ::SSN::debugging; // Without this,Bob::debugf is used
::SSN::debugging:: // Disambiguate
debugf("Frequency::Frequency(Coder: %p,%zd)\n",&_function,sizeof(_function));
// Drive the function,mostly just to see that it's been coded correctly
// Note the existence of function(DeviceFrequency& E) in namespace Bob.
// This *looks* like it might call Bob::function,but it doesn't,// likely because function is an object containing an operator(),a
// std::function container.
Coder coder{2468}; function(coder); // (??TRICKY?? Not ADL ambiguous)
Coder rodoc{8642}; _function(rodoc); // (??TRICKY?? Not ADL ambiguous)
Bob::function(coder); // Just to verify it's different
}
Frequency( void ) // Default constructor
{ using namespace ::SSN::debugging; // Why isn't this enough??
using ::SSN::debugging::debugf; // Or this??
// Answer: When ADL comes into play,using statements are ignored.
// Also,without the first using,Bob::debugf would be used.
// (Rationale unclear,but that's the way it is.)
if( ssn_debug )
#if MAKE_IT_WORK
// As explained by @1201ProgramAlarm,it's the 'this' parameter that
// brought Bob's namespace into play for ADL name resolution.
// (Rationale unclear,but that's the way it is.)
::SSN::debugging:: // How can a developer predict that this is needed??
#endif
debugf("Frequency(%p,%zd)::Frequency\n",this,sizeof(*this));
// All remaining lines in Frequency added for detailed testing ============
const void* const that= this; // Another work-around
debugf("Frequency(%p,that,sizeof(*this)); // (Works)
Coder coder{3.14}; // (This is a Bob::DeviceFrequency)
debugf("Coder.f: %f\n",coder.f); // (No ambiguity)
Bob::debugf("Coder: %f\n",coder); // *** AMBIGUOUS debugf ***
debugf("f: %f\n",f); // (No ambiguity)
debugf("thing: %f\n",thing); // (No ambiguity)
debugf("Bob::thing: %f\n",Bob::thing); // (No ambiguity)
debugf("bar.bar: %d\n",bar.bar); // (No ambiguity)
SSN::debugging::debugf("bar: %d\n",bar); // *** AMBIGUOUS debugf ***
Bob::debugf("this->bar: %d\n",this->bar); // *** AMBIGUOUS debugf ***
debugf("foo.foo: 0x%3x\n",foo.foo); // (No ambiguity)
debugf("this->foo: 0x%3x\n",this->foo); // (No ambiguity)
debugf("Bob::bar.bar: %d\n",Bob::bar.bar); // (No ambiguity)
Bob::debugf("Bob::bar: %d\n",Bob::bar); // *** AMBIGUOUS debugf ***
debugf("secret.bar: %d\n",secret.bar); // (No ambiguity)
Bob::debugf("secret: %d\n",secret); // *** AMBIGUOUS debugf ***
debugf("content: %d\n",content); // (No ambiguity)
Bob::debugf("content.m: %d\n",content.m); // *** AMBIGUOUS debugf ***
// End of added lines =====================================================
}
}; // class Frequency
template<typename Coder>
struct Ugly_fix { // Macros stop ADL lookup,remove usings
#define debugf ::SSN::debugging::debugf
#define ssn_debug ::SSN::debugging::ssn_debug
Ugly_fix( void ) // Default constructor
{ if( ssn_debug ) debugf("Ugly_fix(%p)::Ugly_fix\n",this);
debugf("Bob::bar: %d\n",Bob::bar);
// Bob::debugf("Bob::bar: %d\n",Bob::bar); // Syntax error
}
#undef debugf // Macros (MUST BE) private
#undef ssn_debug
}; // struct Ugly_fix
class Non_template { public:
Non_template( void )
{ using namespace ::SSN::debugging;
if( ssn_debug )
debugf("Non_template(%p,%zd)::Non_template\n",sizeof(*this));
}
}; // class Non_template
} // namespace SSN::signals
// Namespace: **NONE**
static struct Handler { // Note: This actually *USES* ADL to find debugf
void operator()(Bob::DeviceFrequency& E) { debugf("E: %f\n",E); }
} handler; // struct Handler
struct Controller {
SSN::signals::Frequency<Bob::DeviceFrequency> bobcon;
SSN::signals::Frequency<Bob::DeviceFrequency> robcon;
SSN::signals::Ugly_fix<Bob::DeviceFrequency> ugly;
SSN::signals::Non_template non_template;
// Note that both Frequency constructors are used.
Controller( void ) : bobcon(),robcon(handler),ugly(),non_template() {}
}; // class Controller
// IMPLEMENTATION ============================================================
namespace SSN {
int debugging::ssn_debug= true;
namespace debugging {
void debugf(const char* fmt,...) {
printf("SSN: "); // (For output disambiguation)
va_list argptr; va_start(argptr,fmt); vprintf(fmt,argptr); va_end(argptr);
} // debugf
} // namespace debugging
} // namespace SSN
namespace Bob {
double thing= 1122.33; // (Added for answer testing)
Foo bar{732}; // (Added for answer testing)
void debugf(const char* fmt,...) {
printf("Bob: "); // (For output disambiguation)
va_list argptr; va_start(argptr,argptr); va_end(argptr);
} // debugf
} // namespace Bob
// TEST ======================================================================
static inline int // Number of errors encountered
test_namespace_glitch( void ) // Test namespace glitch
{
int errorCount= 0; // Number of errors encountered
printf("\ntest_namespace_glitch\n");
Controller controller; // (Actually drive the constructor)
if( true ) {
errorCount++; // *Why* does 'this' import namespace?
printf("Still (partially) unexplained\n");
}
return errorCount;
}
(对于那些喜欢运行代码的人,大多数测试包装器也都包括在这里。您只需要一个调用测试的主程序。)
以下是我需要注意的事项:
- 将
this
用作模板代码内部的(裸)参数。 (感谢@1201ProgramAlarm指出了此“功能”。)很容易意外地做到这一点,但很难弄清它为什么会发生。 - 引用(不受控制的)命名空间对象(即结构或类)用作函数的参数,无论它是否在模板代码中。这很难做到。
- 此引用可以是间接引用,例如
Frequency(const Function&)
构造函数中的引用,因此更容易意外使用。
这可能是(也可能不是)ADL“陷阱”的完整列表。 https://en.cppreference.com/w/cpp/language/adl中有很多示例让我头晕目眩,但工作的人通常似乎并不是一旦被ADL咬伤就可能会意外地做这些事情。
我认为,没有明确的意图就不应发生ADL。需要一些语言关键字或信号,例如[[adl]]std::swap(obj1,obj2)
来激活它。我想那只是我。
我不知道或不了解ADL设计决策背后的基本原理,即忽略使用语句,而将裸 this 参数视为对模板对象的引用。我确定(希望,希望)它们存在。您可能会对安德鲁·科尼格(Andrew Koenig)的简短评论A Personal Note About Argument-Dependent Lookup感兴趣,该评论是在深入探讨cppreference.com中ADL的奥秘时发现的。
为了便于记录,我在投票并接受@1201ProgramAlarm的答案,因为他解释说 this 参数使ADL发挥了作用。当明确寻找它时,我没有找到任何文档说明会发生这种情况。它没有回答我的问题,“如何防止这种情况发生?”没有它,我就无法开始。
有用的是,您收到一个看似无法解释的“歧义参考”错误消息,让您知道一些奇怪而糟糕的事情。对于必须解决此问题的人员而言,为时已晚,也太糟糕了。对于那些不考虑ADL的人来说太糟糕了,可能不得不花大量的时间首先尝试找出问题的根源,甚至到最后再不得不向stackoverflow寻求帮助,因为ADL太奇怪了。em>
我有一个丑陋的方法来绕过ADL,如Ugly_fix
构造函数所示。通过使用(ugh)宏自动神奇地完全限定名称,不仅可以防止ADL,而且不再需要using语句。这种宏必须是临时的,使用后未定义,以避免污染宏名称空间。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。