【问题标题】:Point of customization - namespace injection定制点 - 命名空间注入
【发布时间】:2018-01-04 13:43:59
【问题描述】:

首先是冗长的解释,其次是实际问题:

在 C++ 库中,我想提供自定义点。也就是说,某些方法可以由用户“注入”。通常,这是通过 ADL 以下列方式完成的:

文件operators.h 包含:

namespace operators
{
    namespace print_overloads
    {
        void print_value(double x)
        {
            cout << x << endl;
        }
    }

    namespace detail
    {
        template <typename Value>
        void adl_print(Value x)
        {
            using print_overloads::print_value;
            print_value(x);
        }
    }

    template <typename Value>
    void print(Value x)
    {
        detail::adl_print(x);
    }
}

print_value() 通过 ADL 提供自定义点。要使用它,可能有testi.cpp

#include "operator.h"
namespace custom
{
    struct A {};

    void print_value(A)
    {
        cout << "ADL A overload" << endl;
    }
}


int main()
{
    operators::print(custom::A{});
}

这按预期工作。但是,只有当用户可以在相应的命名空间(本例中为namespace custom)中定义函数时才适用。

我的想法是在上面的示例中引入一个专用的重载命名空间namespace print_overload。对于用户来说,这应该允许:

#include "operators.h"
namespace custom_inaccessible
{
    struct A {};
}

namespace operators::print_overloads
{
    void print_value(custom_inaccessible::A)
    {
        cout << "A overload" << endl;
    }
}


int main()
{
    operators::print(custom_inaccessible::A{});

    int pause;
    std::cin >> pause;
    return 0;
}

很遗憾,这不起作用。当前的 Microsoft Visual Studio 2017 C++ 编译器失败:

error C2664: 'void operators::print_overloads::print_value(double)': cannot convert argument 1 from 'testi::B' to 'double'

似乎print_value(A) 的重载不在重载列表中考虑。经过一番修改,我发现一致性模式设置设置为Yes(/permissive-)。如果我将其设置为 No 一切正常。

现在回答问题:

  • 标准中是否定义了这种行为?
  • 如果是这样,该标准的哪一部分对这个结果负责?
  • 还是 Visual Studio 一致性模式的一些错误?

【问题讨论】:

  • coliru.stacked-crooked.com 上尝试使用 gcc 和 clang 的代码。但看起来一致性模式设置具有正确的行为,而非一致性模式不正确,因为在解析函数调用时,只考虑先前声明的函数,直到最近 MSVC 对模板出错。
  • 我会说使用 ADL 来做这件事是相当不寻常的。最好引入一些可以由用户专门化的类型特征或接受一些具有默认值的额外模板参数。
  • @VTT 我指的是这个:ericniebler.com/2014/10/21/…

标签: c++ visual-studio-2017 overloading


【解决方案1】:

为回答您对标准的疑问,相关段落位于[temp.dep.candidate]

对于后缀表达式是从属名称的函数调用, 使用通常的查找规则找到候选函数 ([basic.lookup.unqual], [basic.lookup.argdep]) 除了:

  • 对于使用非限定名称查找的部分查找,只能找到来自模板定义上下文的函数声明。
  • 对于使用关联命名空间 ([basic.lookup.argdep]) 进行查找的部分,仅在两者中找到的函数声明 模板定义上下文或模板实例化上下文 找到了。

上面的意思是,任何因using print_overloads::print_value;(根据第一个项目符号的非限定名称查找)而找到的东西都必须在定义模板时已经存在。用户不能只是重新打开命名空间然后再添加它。

MSVC 在一致性模式下拒绝它是非常正确的。


详细来说,using 声明通过限定名称查找引入名称,根据[namespace.udecl]/1

using-declaration 中的每个 using-declarator 都引入了一组 声明进入声明区域,其中 使用声明出现。引入的声明集 using-declarator 是通过对 using-declarator 中的名称,不包括隐藏为的函数 如下所述。

但是,using 声明只是引入了非依赖名称。并根据[temp.nondep]/1

模板定义中使用的非依赖名称使用 通常的名称查找并在它们使用时绑定。

“使用点”是模板定义的点。

【讨论】:

  • 我看不到using print_overloads::print_value; 是一个不合格的名称查找。根据en.cppreference.com/w/cpp/language/unqualified_lookup,“对于非限定名称,即没有出现在范围解析运算符 :: 右侧的名称”,这应该是限定名称查找?
  • @LcdDrm - print_value(x); 是非限定名称查找(对于 print_value)。通过 using 声明提供给它的名称必须事先声明。
猜你喜欢
  • 2020-09-10
  • 2016-12-03
  • 2016-04-10
  • 2019-04-26
  • 1970-01-01
  • 2016-04-28
  • 2011-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多