Search code examples
c++gcc-warning

What does "ignoring attributes on template argument" mean in this context?


I've been tasked with eliminating the warnings in a largish C++ project, most of which I did not write, and one of the more common warnings is emitted for things like this:

std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(dirToDelete.c_str()), closedir);

The warning is

ignoring attributes on template argument 'int (*)(DIR*) {aka int(*)(__dirstream*)}' [-Wignored-attributes]
std::unique_ptr dir(opendir(dirToDelete.c_str()), closedir);

(DIR, opendir, and closedir are all defined in the standard header dirent.h.)

I know what this code does, and it is working, but what does the warning mean? What is the attribute that the compiler is referring to that is ignored, and what are the implications of that?


Solution

  • The attribute which is being ignored is likely GCC's nonnull, which indicates that parameters cannot be null pointers. You can reproduce this warning with the following code:

    #include <memory>
    
    void closedir(struct DIR*) __attribute__((nonnull(1)));
    
    std::unique_ptr<struct DIR, decltype(&closedir)> dir(nullptr, &closedir);
    
    <source>:5:48: warning: ignoring attributes on template argument 'void (*)(DIR*)' 
                   [-Wignored-attributes]
        5 | std::unique_ptr<struct DIR, decltype(&closedir)> dir(nullptr, &closedir);
          |
    

    The problem is that the attributes of a function pointer are discarded when being used as template arguments. If you kept using function pointers, you would have to get rid of the attribute (see Remove __attribute__((...)) from a function pointer or reference), or you would have to suppress the warning (see Selectively remove a warning message using GCC).

    Solution

    In general, you should not create smart pointers where the deleter is a function pointer, because that increases the size of the smart pointer1), and prevents optimizations2). You are creating a std::unique_ptr that can hold a deleter with the same signature as closedir, but the deleter could be any function in principle.

    Instead, prefer custom types as deleters:

    #include <memory>
    
    // Exposition-only, obviously use #include <dirent.h>
    void closedir(struct DIR*) __attribute__((nonnull(1)));
    
    
    // Note 1: In C++20, you could also decltype(lambda) as a deleter type.
    struct closedir_deleter {
        // Note 2: Consider adding noexcept.
        void operator()(struct DIR* d) const {
            closedir(d);
        }
    };
    
    // Note 3: You can create a type alias for this specialization of std::unique_ptr.
    // Note 4: It is no longer necessary to pass a deleter as an argument to the
    //       constructor, because the deleter is default-constructible, and not a pointer.
    std::unique_ptr<struct DIR, closedir_deleter> dir;
    

    See live example at Compiler Explorer

    Not only is this better practice, it also gets rid of the warning for you.


    1) This is because std::unique-ptr has to store the pointer itself, as well as the function pointer. For simple empty deleters, it doesn't have to store anything.

    2) Function pointers cannot be optimized into direct function calls if the compiler doesn't know their exact value. Function pointers are known to be inlined poorly in general.