代码之家  ›  专栏  ›  技术社区  ›  fredoverflow

日常生活能力的陷阱是什么?

  •  48
  • fredoverflow  · 技术社区  · 15 年前

    2 回复  |  直到 15 年前
        1
  •  75
  •   James McNellis    15 年前

    #include <iostream>
    
    namespace utility
    {
        template <typename T>
        void print(T x)
        {
            std::cout << x << std::endl;
        }
    
        template <typename T>
        void print_n(T x, unsigned n)
        {
            for (unsigned i = 0; i < n; ++i)
                print(x);
        }
    }
    

    很简单,对吧?我们可以打电话 print_n() print 打印对象 n 次。

    完全不知道 调用什么函数 print_n . 可能是 打印 函数模板,但可能不是。为什么?参数相关查找。

    namespace my_stuff
    {
        struct unicorn { /* unicorn stuff goes here */ };
    
        std::ostream& operator<<(std::ostream& os, unicorn x) { return os; }
    
        // Don't ever call this!  It just crashes!  I don't know why I wrote it!
        void print(unicorn) { *(int*)0 = 42; }
    }
    

    接下来,编写一个小程序,创建一只独角兽并打印四次:

    int main()
    {
        my_stuff::unicorn x;
        utility::print_n(x, 4);
    }
    

    你编译这个程序,运行它,然后。。。它崩溃了。”什么?!“不可能,”你说:“我刚打电话来 打印\n ,称为 打印 打印 你希望它调用的函数。它叫 my_stuff::print

    为什么是 我的东西:打印 挑选出来的?在名称查找期间,编译器会看到 属于类型 unicorn ,这是在命名空间中声明的类类型 my_stuff .

    打印 ,然后在重载解析期间将其选为最佳可行候选:不需要转换即可调用任何一个候选 打印 函数和非模板函数优先于函数模板,因此非模板函数 我的东西:打印 是最好的搭配。

    是的,依赖于参数的查找是C++的一个重要特性。基本上需要实现一些语言特性(如重载运算符)的期望行为(考虑streams库)。也就是说,这也是非常,非常有缺陷的,可能会导致非常丑陋的问题。已经有好几个建议来解决依赖于参数的查找,但是没有一个被C++标准委员会接受。

        2
  •  4
  •   FrankHB    7 年前

    公认的答案是完全错误的-这不是一个错误的日常生活能力。在日常编码中使用函数调用是一种粗心的反模式——忽略依赖名,盲目依赖不合格的函数名。

    postfix-expression 在函数调用中,你 已经承认您已经授予了在其他地方可以“重写”函数的能力(是的,这是一种静态多态性)。因此,C++中函数的非限定名称的拼写完全是 接口

    在接受答案的情况下,如果 print_n 真的需要ADL吗 print 打印 打印 my_stuff . 否则,它就是 . 解决方法很简单:限定 打印 带前缀 utility:: . 这确实是一个错误 打印\n ,但几乎没有语言中的ADL规则的缺陷。

    只有一个 . 他们实现了超过10年,但没有在语言是固定的。他们被公认的答案遗漏了(除了最后一段到目前为止是完全正确的)。看到这个了吗 paper

    is_nothrow_swappable 哪里 __cplusplus < 201703L swap 我的命名空间中的函数模板。这样的 互换 std::swap using std::swap; 在ADL规则下使用ADL,就会产生歧义 互换 互换 模板(将实例化 不可交换吗 noexcept-specification 互换 包括模板。所以,除非我超载 我的图书馆类型与专业 互换 函数(用于抑制任何候选泛型模板 互换 由于在ADL之后被重载解析匹配,我无法声明模板。讽刺的是 互换 boost::swap )它是最重要的直接客户之一 不可交换吗 boost::交换 不遵守异常规范)。这完全打破了我的目的,叹息。。。

    #include <type_traits>
    #include <utility>
    #include <memory>
    #include <iterator>
    
    namespace my
    {
    
    #define USE_MY_SWAP_TEMPLATE true
    #define HEY_I_HAVE_SWAP_IN_MY_LIBRARY_EVERYWHERE false
    
    namespace details
    {
    
    using ::std::swap;
    
    template<typename T>
    struct is_nothrow_swappable
        : std::integral_constant<bool, noexcept(swap(::std::declval<T&>(), ::std::declval<T&>()))>
    {};
    
    } // namespace details
    
    using details::is_nothrow_swappable;
    
    #if USE_MY_SWAP_TEMPLATE
    template<typename T>
    void
    swap(T& x, T& y) noexcept(is_nothrow_swappable<T>::value)
    {
        // XXX: Nasty but clever hack?
        std::iter_swap(std::addressof(x), std::addressof(y));
    }
    #endif
    
    class C
    {};
    
    // Why I declared 'swap' above if I can accept to declare 'swap' for EVERY type in my library?
    #if !USE_MY_SWAP_TEMPLATE || HEY_I_HAVE_SWAP_IN_MY_LIBRARY_EVERYWHERE
    void
    swap(C&, C&) noexcept
    {}
    #endif
    
    } // namespace my
    
    int
    main()
    {
        my::C a, b;
    #if USE_MY_SWAP_TEMPLATE
    
        my::swap(a, b); // Even no ADL here...
    #else
        using std::swap; // This merely works, but repeating this EVERYWHERE is not attractive at all... and error-prone.
    
        swap(a, b); // ADL rocks?
    #endif
    }
    

    尝试 https://wandbox.org/permlink/4pcqdx0yYnhhrASi 然后转身 USE_MY_SWAP_TEMPLATE true 去看那些模棱两可的东西。

    啊哈,今天早上我又被ADL咬了。这一次它甚至与函数调用无关!

    今天我完成了移植的工作 ISO C++17 std::polymorphic_allocator this ),这次我只是用别名模板替换声明,如:

    namespace pmr = ystdex::pmr;
    template<typename _tKey, typename _tMapped, typename _fComp
        = ystdex::less<_tKey>, class _tAlloc
        = pmr::polymorphic_allocator<std::pair<const _tKey, _tMapped>>>
    using multimap = std::multimap<_tKey, _tMapped, _fComp, _tAlloc>;
    

    ... 所以它可以使用 my implementation of polymorphic_allocator

    但它突然不起作用,有数百行神秘的错误信息。。。

    this line . 它粗略地抱怨说 BaseType 不是封闭类的基 MessageQueue . 这看起来很奇怪,因为别名是用与 基本说明符列表 我确信它们中没有任何一个可以被宏扩展。那为什么呢?

    答案是。。。日常生活能力很差。 The line inroducing BaseType std 名称作为模板参数,因此将根据ADL规则查找模板 在类范围内 . 因此,它发现 std::multimap 在封闭的命名空间范围中 . 自 使用 std::allocator 实例作为默认模板参数, 多态分配程序 ,偶数 multimap 在封闭命名空间中声明的将重定向到 . 通过将封闭限定符作为前缀添加到 =

    我承认我很幸运。错误消息将问题引至此行。只有两个类似的问题 the other 没有任何明确的 标准 (其中 string my own one 适应ISO C++ 17 string_view 改变,而不是 标准 一个在前C + + 17模式)。我不会这么快就发现这个虫子是关于日常生活能力的。