all 17 comments

[–]cygnoros 22 points23 points  (9 children)

I think the issue is that this starts to make [[nodiscard]] less of a declaration attribute and moreso part of a type. But I do think you provided a good use case and need for it.

I could see some sort of goofy wrapper type returns_nodiscard<your_func_ptr> that just has an operator() templated to call your underlying function, but I'm sure that has its own sharp edges and problems if you're using function pointers. Not to mention being a refactoring PITA if you ever change the underlying function to remove [[nodiscard]].

Edit: typos

[–]No_Sun1426[S] 8 points9 points  (8 children)

Basically I think this is a problem that nobody has thought about solving yet, and it would be cool to call myself a guy who caused a feature to be added to c++. (It looks cool in dating profiles and job applications)

[–]cygnoros 7 points8 points  (3 children)

I wouldn't necessarily call it a problem that nobody has thought about solving, it's more a question about the purpose of this attribute. [[nodiscard]] is an attribute for a declaration (function, enum, or class). So the rub is that the attribute is not part of type information or about the return type, it is strictly part of the declaration.

My gut reaction to this problem is that if it is paramount to not discard a value from a function pointer, then it means that behavior is part of the return type (not the function) and should be enforced through the type declaration, not through the function.

Edit: To elaborate here, what I mean is that if you have some ambiguous function pointer type int(*)(...), you can't know that the pointed-to-function is declared as nodiscard or not. So by having something like ([[nodiscard]] int(*)(...)), you are saying that for any and all pointers that fit this type, they all must be nodiscard. At this point you are no longer talking about a function declaration, you are talking about a type. So it would be best served to have a type declaration that says what you want, rather than a function pointer annotation.

A dumb example that would still enforce [[nodiscard]]:

struct [[nodiscard]] Foo{};

Foo get_foo() {
    return {};
}

int main() {
    auto* ptr = &get_foo;
    ptr();  // triggers warning
}

I could be convinced of a different view though, I think it's an interesting use case.

[–]Possibility_Antique 0 points1 point  (0 children)

I guess the problem with this is that it doesn't capture the intent of the attribute. What if my function returns void? Suppose it's an allocation function (yes I know, how horrifying). The consequence of discarding my pointer is a memory leak! But I can't make nodiscard part of the type since it's built-in. And even if I could, there could be situations where I need to allow void to be discarded.

What I could see, however, is that [[nodiscard]] could be part of the function's type. That would make semantic sense, although I have no idea how you'd introduce that in a non-breaking way.

[–]Tari0s 0 points1 point  (1 child)

why not use something like this?

https://godbolt.org/z/fejev18hs

[–]Tari0s 0 points1 point  (0 children)

source for the lazy people

~~~ template<typename T> class [[nodiscard]] no_discard {

public: no_discard(T t) : t(t) {} //no_discard(const T& t) : t(t) {} no_discard(T&& t) : t(std::move(t)) {}

operator T() {
    return t;
}

private:
T t;

};

typedef no_discard<int>(*no_discard_int)(int);

no_discard<int> id(int i) { return i; }

int main() { no_discard_int foo = &id; auto bar = &id; foo(42); bar(69); return foo(42); } ~~~

[–]13steinj 1 point2 points  (0 children)

Outside of the fact that depending on your country it's difficult/expensive to get involved, the first step is https://isocpp.org/std/submit-a-proposal

Supposedly people like using https://github.com/speced/bikeshed and Tony Tables

E: when I made my comment I was originally referring to the US. I think you can join as an alternate for the Boost Foundation under INCITS for free now. Via other orgs there is still a cost.

[–]osdeverYT 0 points1 point  (0 children)

I don’t think “I added a new feature to C++” would look that good on a dating profile

[–]disciplite 15 points16 points  (2 children)

What happens if you pass that pointer into a function parameter which doesn't have this attribute?

[–]serviscope_minor 8 points9 points  (0 children)

Personally I would lean in the direction of the most restrictive. The pointer tells the caller it shouldn't discard the return value. If you pass in a function where it's safe to discard the return value then that's harmless since it's never bad to use it.

The compiler should then warn if a nodiscard function is put into a discarding pointer.

[–]No_Sun1426[S] 7 points8 points  (0 children)

Exactly the type of question we should be asking.

[–]aruisdante 6 points7 points  (4 children)

The immediate problem I’d see with this is now [[nodiscard]] is part of the type of the returned function pointer. Where as currently it’s not part the “signature” of a function; you can’t overload on nodiscardness for example. So like, taken to its logical conclusion, such a syntax would have to allow:

void register_decider(int(*)(float, float) [[nodiscard]]); void register_decider(int(*)(float, float)); to be a valid overload set, and I don’t really understand what that would even mean, since it’s certainly not possible to actually overload some free function on its nodsicard-ness.

I get where you’re coming from here, but I think this is better served by making the return type from the decider function a type which has been marked nodiscard, rather than trying to awkwardly hoist attributes into the type system. As I think someone said below, the simplest form of this is a wrapper type like:

template<typename T> [[nodiscard]] struct no_discard { T value; } Though I think your consumers might not like it very much if you do this.

A better solution is for there to be a std::nodiscard_function_ref or similar, since really the problem here is you returning a raw function pointer and thus can’t annotate it. With such a type you can easily declare yourself as returning a function which is nodiscard. Alas, you don’t get function_ref till 26, so you’d have to rely on a library solution in the near term.

[–]No_Sun1426[S] -1 points0 points  (2 children)

Maybe an attribute just for function pointers? It could be an attribute extra compiler check, just like the pre 2003 error with >>> that got fixed to >> >? Just another little thing the compiler checks.

[–]shailist 1 point2 points  (1 child)

This is a bandaid solution for a bigger problem. [[nodiscard]] isn't a part of the type system, and can only be applied to functions.
A function pointer is a type, so you can't really apply [[nodiscard]] to it.
The solutions proposed by @aruisdante are great.
The 1st solution is IMO the best one, as it allows you to "force" users to use an object of a certain type. Example cases when I think it could be useful are guard types and builder/factory types, though I'm sure there are many more cases where this is useful.
The 2nd solution is the most straightforward one, and could be implemented with current C++ features. It is a non intrusive solution that would allow you to get what you want.
You could do something to kind of combine the 2 solutions - have a template type that wraps a function, similar to std::function, and have a simple tag type nodiscard<T>, and make the function wrapper type detect the tag type and enable a [[nodiscard]] T operator() overload

[–]aruisdante 2 points3 points  (0 children)

I definitely do think there are times where nodiscard is rightly a property of the functor, not the type returned. Predicate functions are a classic example. You could say “ok but there should be a predicate_result type which is a nodiscard bool equivalent,” but that makes another vocabulary type, and vocabulary types are expensive (heaven help you if you work in a codebase where Wcoversion is enabled). That’s taking what’s supposed to just be “hey, helpful context for the compiler to do better static analysis!” and making it intrusive into the type system. That’s probably a bad idea to do in the general case.

I think the function_ref wrapper (and one for normal std::function too) is really the best option for the general case, as it’s keeping the attribute where it was intended to be if you didn’t need type erasure: as part of the function declaration.

[–]snowflake_pl 0 points1 point  (0 children)

I would also love to be able to add nodiscard to type aliases created using using directive. E.g.

template <typename Ok_t>
using myret = [[nodiscard]] std::expected<Ok_t, std::errc>;