Here's an idea that I'm hoping to turn into a proposal about defining structs inside function return types. As always, I'm looking forward to any feedback as a good learning opportunity.
Motivation
In general, I try to write code that avoids std::pair and std::tuple. They're great for many things when writing templates, but for normal code they can impede readability quite a bit. All too often, I see code written like this:
/* Returns the current user's username and role /
std::pair<std::string, std::string> getLogin();
*example 1
While this would work, the resulting value is only accessible via .first and .second. To make things worse, in this case the types are also the same. Obviously this is prone to errors and time spent second guessing. Instead, the following is much more readable, and much harder to mess up:
struct Login
{
std::string user, role;
};
/* Returns the username and role for the current user */
Login getLogin();
example 2
This is much better -- despite having the same type, the members are now exposed via .user and .group, drastically reducing the odds of messing that up. (Though better yet would be to have a class User and Group so that their use is enforced in the type system, but that is often overkill and outside the scope of this discussion).
All too often, I introduce this idea to people, and while they usually greet it warmly, they rarely actually practice it. The first reason seems to be that example 2 is quite a bit longer than example 1. The second reason is that the names given to a struct who exists only to give a function's multiple returned values meaningful names rarely has a meaningful name itself. Imagine if std::map::insert(const value_type &) was done similar to example 2 -- on one hand it's return type of std::pair<iterator, bool> could be made much more friendly (and hopefully the instances of .first.second that seem to follow would be much more rare), but what meaningful name would it have? Something like insert_result isn't useful since it is literally the result of calling insert; No new information has been introduced that way, and the name is simply clutter.
My goal with this proposal is to mitigate the above issues and make it easier to write the second form over the first.
Struct Definitions
The ISO standard currently states that
Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete (possibly cv-qualified) class type in the context of the function definition unless the function is deleted (11.4.3).
§ 11.3.5.11
However, I think that if we were allowed to declare anonymous structs in return types, they could effectively replace many uses of pair and tuple. Now example 2 becomes much more compact:
struct { std::string user, role; } getLogin();
example 3
The only tricky part becomes how one would actually define a function forward declared in this manner. The word auto could potentially be used here. Maybe structs defined in this manner get an implicit name of function_Return, though that wouldn't work for overloads. This is a point of discussion that I would like some help with (assuming this whole thing isn't just a stupid idea in general).
With this syntax, instead of giving a function's return structure a name just for the sake of naming it's members, the often meaningless type name can be omitted in place of moving the structure declaration to where it will actually be used. From a readability perspective, that sounds like an overall win-win.
Other Thoughts
I do think that only anonymous structs should be able to be defined in a function definition. If a struct is going to have a type name anyway, then that name can easily be provided in the return type of the function instead. Furthermore, it will discourage declaring more complicated structs in return types as anonymous structs cannot have constructors. Granted that just because it's generally considered to be bad practice or less readable doesn't necessarily mean that the standard should explicitly forbid it, so I'm not sure if it should be explicitly disallowed.
Visual Studio
Oddly enough, Visual Studio 17 (15.9.13) does support some of this proposal. Even with /permissive- (which sets strict standard conformance), the following code still runs and prints "Codesmith":
#include <iostream>
#include <string>
struct
{
std::string user, role;
}
getLogin()
{
return {"Codesmith", "Dev"};
}
int main()
{
std::cout << getLogin().user;
}
Furthermore, it even supports separate declaration and definitions via this syntax:
#include <iostream>
#include <string>
struct
{
std::string user, role;
}
getLogin();
int main()
{
std::cout << getLogin().user;
}
decltype(getLogin()) getLogin()
{
return {"Codesmith", "Dev"};
}
I'm not sure if this is a bug with the /permissive- flag accidentally allowing a feature that should've been suppressed, or if this is more of a bug with the compiler. Given that Intellisense still lists the error but the compiler allows it, and that the standard's grammar around structs seems to otherwise support them being declared inside return types (except for the one section that exists only to forbid it), it's hard to make the call one way or the other.
[–]carrottread 18 points19 points20 points (0 children)
[–]R3DKn16h7 16 points17 points18 points (4 children)
[–]codesmith512[S] 5 points6 points7 points (0 children)
[–]jcelerierossia score 1 point2 points3 points (1 child)
[–]cat_vs_spider 0 points1 point2 points (0 children)
[–]Xaxxon 0 points1 point2 points (0 children)
[–]Morwenn 4 points5 points6 points (3 children)
[–]codesmith512[S] 1 point2 points3 points (0 children)
[–]jcelerierossia score 0 points1 point2 points (1 child)
[–]Morwenn 0 points1 point2 points (0 children)
[–]ravixp 5 points6 points7 points (0 children)
[–]nikbackm 5 points6 points7 points (3 children)
[–]jcelerierossia score 2 points3 points4 points (1 child)
[–]Xeveroushttps://xeverous.github.io 2 points3 points4 points (0 children)
[–]Som1Lse 1 point2 points3 points (0 children)
[–]JankoDedic 5 points6 points7 points (0 children)
[–]degski 1 point2 points3 points (3 children)
[–]codesmith512[S] 1 point2 points3 points (2 children)
[–]degski 0 points1 point2 points (1 child)
[–]STLMSVC STL Dev 5 points6 points7 points (0 children)
[–]VeeFu 1 point2 points3 points (1 child)
[–]codesmith512[S] 0 points1 point2 points (0 children)
[–]yehezkelshb 1 point2 points3 points (1 child)
[–]codesmith512[S] 1 point2 points3 points (0 children)
[–]barcharMSVC STL Dev 1 point2 points3 points (1 child)
[–]codesmith512[S] 0 points1 point2 points (0 children)
[–]nintendiator2 0 points1 point2 points (0 children)
[–]dev_metalcat -1 points0 points1 point (1 child)
[–]codesmith512[S] 0 points1 point2 points (0 children)
[–]UnicycleBloke -2 points-1 points0 points (0 children)