7

I have a functional object which is a wrapper around another function:

template <typename FuncT>
class Wrapper
{
    private:
        FuncT funcToWrap;

    public:
        Wrapper(FuncT ftw) : funcToWrap(ftw){};

        template<typename ...ARG>
        typename std::result_of<FuncT(ARG&&...)>::type operator()(ARG&&... args){
            return funcToWrap(std::forward<ARG>(args)...);
        }
};

int main(){
    std::function<void()> testfunc = [](){ std::cout << "Test" << std::endl; };
    Wrapper<decltype(testfunc)> test{testfunc};
    test();
}

What I would like to do is to mark the operator() as [[nodiscard]] if the std::result_of<FuncT(ARG&&...)>::type is not void.

What I have noticed is that when I do put the [[nodiscard]] in case of the template evaluation of return type to void, it will simply get ignored by my compiler.

Is this the behaviour I can rely on, is it in any way standarized?

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
cerkiewny
  • 2,761
  • 18
  • 36
  • 1
    This is clearly [not guaranteed by the standard](http://eel.is/c++draft/dcl.attr.nodiscard), but I find no documentation from gcc or clang about void nodiscard :( – YSC Jun 12 '19 at 08:43
  • Yea, me neither, but for gcc version 7.4.0 it works, I was quite surprised actually that it does, thought I will need to template specialize for void return. – cerkiewny Jun 12 '19 at 08:44
  • 1
    `std::result_of` is [deprecated in C++17 and removed in C++20](https://en.cppreference.com/w/cpp/types/result_of). Use `std::invoke_result` instead. – metalfox Jun 12 '19 at 08:44
  • 1
    It appears to be unspecified by the standard. But honestly, I'd just put the attribute there and not fret. Attributes are non-binding hints to the compiler. So long as they are syntactically correct, they will not make an otherwise well-formed program into an ill-formed one. – StoryTeller - Unslander Monica Jun 12 '19 at 08:45
  • 1
    I wonder if it should be reported as a documentation bug... – YSC Jun 12 '19 at 08:45
  • @YSC I will have a look on how to do it and try and ask on discussion group – cerkiewny Jun 12 '19 at 08:50
  • 1
    Working around the lack of guarantees: https://wandbox.org/permlink/xAQvlhtB6CXU8YnV – metalfox Jun 12 '19 at 15:35
  • @metalfox: You should post that as an answer – Eric Sep 22 '19 at 08:43
  • @Eric OK. Posted – metalfox Sep 25 '19 at 06:34

2 Answers2

2

You can use SFINAE to select between two overloaded operator (): one of them returning void, and another one for the rest of cases annotated with the [[nodiscard]] attribute:

#include <type_traits>
#include <iostream>

template <typename FuncT>
class Wrapper
{
    private:
        FuncT funcToWrap;

    public:
        Wrapper(FuncT ftw) : funcToWrap(ftw) {}

        template <typename ...ARG, typename T = std::invoke_result_t<FuncT, ARG...>>
        std::enable_if_t<std::is_void_v<T>> operator()(ARG&&... args) {
            std::cout << "Test 1" << std::endl;
            return funcToWrap(std::forward<ARG>(args)...);
        }

        template <typename ...ARG, typename T = std::invoke_result_t<FuncT, ARG...>>
        [[nodiscard]] std::enable_if_t<!std::is_void_v<T>, T> operator()(ARG&&... args) {
            std::cout << "Test 2" << std::endl;
            return funcToWrap(std::forward<ARG>(args)...);
        }
};

int main() {
    auto testfunc1 = [] { };
    Wrapper test1{testfunc1};
    test1(); // <-- No warnings should be issued here

    auto testfunc2 = [] { return 0; };
    Wrapper test2{testfunc2};
    test2(); // <-- Warning issued here
}
metalfox
  • 6,301
  • 1
  • 21
  • 43
  • The whole point of the question is that I didn't want to do the above. This is what I started with and was wondernig if I had to do it or if it was ok to drop sfinea and use [[nodiscard]] void in such case. – cerkiewny Sep 25 '19 at 07:52
  • @cerkiewny The title of the question and the “What I would like to do…” sentence do not suggest so. Anyway, I posted the answer because I was asked for. I can delete it if you think it is neither useful for other users. – metalfox Sep 25 '19 at 11:25
  • It can stay, if someone gets there because of the doubts how to deal with the problem it is useful, however I am just marking that this doesn't answer my concerns, but it is related for sure – cerkiewny Sep 25 '19 at 14:11
1

Per [dcl.attr.nodiscard]/2:

[ Note: A nodiscard call is a function call expression that calls a function previously declared nodiscard, or whose return type is a possibly cv-qualified class or enumeration type marked nodiscard. Appearance of a nodiscard call as a potentially-evaluated discarded-value expression is discouraged unless explicitly cast to void. Implementations should issue a warning in such cases. This is typically because discarding the return value of a nodiscard call has surprising consequences. — end note ]

My reading of this paragraph gives that, given

[[nodiscard]] void f() {}

even

f();

should issue a warning. You have to explicitly cast to void as in

(void) f();

to suppress it. So no, this is not guaranteed by the standard.

It seems to me that the standard simply overlooked this subtlety.

L. F.
  • 19,445
  • 8
  • 48
  • 82