为什么(或何时)模板引入其名称空间?
Why (or when) does a template bring in its namespace?
这是一个例子:
#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,%zd)::Frequency\n", this, 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,%zd)::Frequency\n", 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", this, 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, fmt); vprintf(fmt, 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 指出此“功能”。)很容易不小心这样做,但很难弄清楚为什么会这样。
- 引用(不受控制的)命名空间命名对象(即结构或 class)用作函数的参数 无论它是否在模板内部代码。这很难不小心做到。
- 此引用可以是间接的,如在
Frequency(const Function&)
构造函数中,因此更容易被意外使用。
这可能是也可能不是 ADL“陷阱”的完整列表。 https://en.cppreference.com/w/cpp/language/adl 中有很多例子让我读起来头晕目眩,但工作的例子似乎通常不是你被 ADL 咬伤后可能不小心做的事情。
在我看来,ADL 不应在没有明确意图的情况下发生。应该需要一些语言关键字或信号,如 [[adl]]std::swap(obj1,obj2)
来激活它。但我想那只是我。
我不知道或理解 ADL 设计决定忽略 using 语句并将裸 this 参数视为对模板对象的引用的基本原理。我确定(好吧,希望如此)它们存在。您可能对上面 Andrew Koenig 的简短评论 A Personal Note About Argument-Dependent Lookup, found while diving into the mysteries of ADL in cppreference.com 感兴趣。
此外,郑重声明,我是 up-voting 并接受 @1201ProgramAlarm 的回答,因为他解释说 这个 参数使 ADL 起作用.在明确寻找它时,我没有找到任何说明会发生这种情况的文档。虽然它没有回答我的问题,“我如何防止这种情况发生?”没有它我无法开始。
有用的是,您收到看似无法解释的“模糊引用”错误消息,让您知道发生了一些奇怪和糟糕的事情。对于必须解决问题的人来说,太晚了,太糟糕了。对于那些不考虑 ADL 的人来说太糟糕了,他们可能不得不花费过多的时间来首先尝试隔离问题,甚至最终不得不调用 Whosebug 寻求帮助,因为 ADL 是如此 怪异。
我有一个绕过 ADL 的丑陋方法,显示在 Ugly_fix
构造函数中。通过使用 (ugh) 宏来 auto-magically 完全限定名称,不仅可以防止 ADL,而且不再需要 using 语句。这种宏必须是临时的,使用后未定义,避免宏命名空间污染。
这是一个例子:
#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,%zd)::Frequency\n", this, 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,%zd)::Frequency\n", 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", this, 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, fmt); vprintf(fmt, 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 指出此“功能”。)很容易不小心这样做,但很难弄清楚为什么会这样。 - 引用(不受控制的)命名空间命名对象(即结构或 class)用作函数的参数 无论它是否在模板内部代码。这很难不小心做到。
- 此引用可以是间接的,如在
Frequency(const Function&)
构造函数中,因此更容易被意外使用。
这可能是也可能不是 ADL“陷阱”的完整列表。 https://en.cppreference.com/w/cpp/language/adl 中有很多例子让我读起来头晕目眩,但工作的例子似乎通常不是你被 ADL 咬伤后可能不小心做的事情。
在我看来,ADL 不应在没有明确意图的情况下发生。应该需要一些语言关键字或信号,如 [[adl]]std::swap(obj1,obj2)
来激活它。但我想那只是我。
我不知道或理解 ADL 设计决定忽略 using 语句并将裸 this 参数视为对模板对象的引用的基本原理。我确定(好吧,希望如此)它们存在。您可能对上面 Andrew Koenig 的简短评论 A Personal Note About Argument-Dependent Lookup, found while diving into the mysteries of ADL in cppreference.com 感兴趣。
此外,郑重声明,我是 up-voting 并接受 @1201ProgramAlarm 的回答,因为他解释说 这个 参数使 ADL 起作用.在明确寻找它时,我没有找到任何说明会发生这种情况的文档。虽然它没有回答我的问题,“我如何防止这种情况发生?”没有它我无法开始。
有用的是,您收到看似无法解释的“模糊引用”错误消息,让您知道发生了一些奇怪和糟糕的事情。对于必须解决问题的人来说,太晚了,太糟糕了。对于那些不考虑 ADL 的人来说太糟糕了,他们可能不得不花费过多的时间来首先尝试隔离问题,甚至最终不得不调用 Whosebug 寻求帮助,因为 ADL 是如此 怪异。
我有一个绕过 ADL 的丑陋方法,显示在 Ugly_fix
构造函数中。通过使用 (ugh) 宏来 auto-magically 完全限定名称,不仅可以防止 ADL,而且不再需要 using 语句。这种宏必须是临时的,使用后未定义,避免宏命名空间污染。