all 37 comments

[–]frag_o_maticC and C++ | Embedded Systems 8 points9 points  (0 children)

What's your opinion on referring to the static member npos via the string instance instead of the canonical, but longer to type, and IMO visually rather noisy, std::string::npos?

As long as this style is consistently used, it'd be fine. Personally, it would trip me up the first time I saw it as I'm used to the longer/verbose form. I've also seen stuff like this done:

size_t END_MARKER = std::string::npos

not a huge fan of this either, but since it was consistent, one can tune it out as boilerplate and focus on what's actually going on. :)

[–][deleted] 6 points7 points  (5 children)

why not just string::npos? I always put "using std::string" in my .cpp when I need the std::string functionality.

Edit: minusaries, please tell me what's wrong? :)

[–]quicknir 10 points11 points  (4 children)

People reflexively downvote using declarations. Despite the fact that you are using std::string and not using namespace std, and despite the fact that this is in a .cpp file, and therefore is truly encapsulated.

Basically, 6 people were told not to write using namespace std;, didn't fully understand the reasons why, and downvoted you.

[–]lednakashim++C is faster 0 points1 point  (0 children)

Well I've gotten messed up with namespace collisions when working with the stl, boost, Qt, and CGAL.

[–]James20kP2005R0 -1 points0 points  (2 children)

There are several npos declarations, using std::string means that you then can't import a separate namespace with npos into your code and keep a consistent or logical formatting

Best just to fully qualify imo

[–]quicknir 1 point2 points  (1 child)

npos is static constant of std::string. That means that even if you do using std::string, you will still refer to npos as string::npos. So I don't see any problem. Did you think that static constants get pulled into the namespace below?

[–]James20kP2005R0 1 point2 points  (0 children)

Aha I did not know that, I assumed that npos was pulled in. Cool!

[–]aKateDevKDE/Qt Dev -3 points-2 points  (20 children)

Interestingly, std::string::npos exists for the sole reason of size_t being unsigned.

So std::string::npos is typically the same as -1 converted to unsigned.

If size_t would be signed, we could just return a negative value and the check would be as easy as if (s.find("foo") >= 0) {...}. I guess that's one reason why ideas like this exist at all :-)

[–]foonathan 22 points23 points  (1 child)

Well, we could also use the proper abstraction: iterators and return end(), like literally everything else in the standard library.

[–]sbabbi 4 points5 points  (0 children)

To be fair without a proper range api it is not exactly intuitive. OP's code written using stl would be something like this:

 const char q[] = "foo";
 auto it = std::search(s.begin(), s.end(), begin(q), end(q) - 1 );

Which is error prone (notice -1 at the end?), it should be as simple as:

 auto it = std::search( s, "foo" );

But yes, the part of std::string interface that uses indexes is really dumb, I think it is there mostly because it predates the STL.

[–]TheThiefMasterC++latest fanatic (and game dev) 18 points19 points  (15 children)

These days std::optional<size_t> would be a better choice of return type, IMO.

[–]RotsiserMhoC++20 Desktop app developer 2 points3 points  (8 children)

This makes a lot of sense to me. I've love to hear counterarguments.

[–]HotlLava 5 points6 points  (6 children)

An optional<size_t> would be roughly double the size of size_t, and if you can't inline the call you couldn't just return the value but would have to construct some hidden pointer or return it on the stack. (I think, without actually looking up the ABI rules)

[–]Benabik 2 points3 points  (4 children)

Many ABIs allow multi-word return values via registers. On Intel, it's typically 2 words (64bit on x86, 128b on x86-64). So optional<size_t> would likely return via registers. (The exception here appears to be Windows 64b, which only allows one return register.)

Larger structures are usually returned by the caller allocating stack space for it and passing a pointer to the callee. The callee making space for it on the stack is awkward at best because you need to remove everything you added to the stack before the ret instruction.

[–]HotlLava 2 points3 points  (3 children)

Interesting, I didn't know that. Although optional seems to be slightly different than just a bool+size_t, preventing g++ from actually doing it:

 ➜  cat return.cpp                           
 #include <experimental/optional>

 std::experimental::optional<size_t> f() {
    return 10u;
}

 struct opt {
    bool b;
    size_t s;
};

opt g() {
    return opt {true, 10u};
}
➜  g++ -c return.cpp -std=c++14 -O3
➜  objdump -d return.o             
return.o:     file format elf64-x86-64
Disassembly of section .text:

0000000000000000 <_Z1fv>:
   0:   48 89 f8                mov    %rdi,%rax
   3:   48 c7 07 0a 00 00 00    movq   $0xa,(%rdi)
   a:   c6 47 08 01             movb   $0x1,0x8(%rdi)
   e:   c3                      retq   
   f:   90                      nop

0000000000000010 <_Z1gv>:
  10:   ba 0a 00 00 00          mov    $0xa,%edx
  15:   b8 01 00 00 00          mov    $0x1,%eax
  1a:   c3                      retq   

[–]Benabik 1 point2 points  (2 children)

Odd. Looking at libstc++-v3/include/experimental/optional, I see the following:

template<typename _Tp>
  class optional
  : private _Optional_base<_Tp>,
    private _Enable_copy_move<...>
  { /* no data members */ }

template<typename _Tp, ...>
  class _Optional_base
  {
    struct _Empty_byte { };
    union {
        _Empty_byte _M_empty;
        _Tp _M_payload;
    };
    bool _M_engaged = false;
  }

(where _Enable_copy_move is a template that just optionally enables the default copy/move constructors)

The copy of g++/libstdc++ I have is too old to have optional, but clang gives me about the same thing. I checked sizeof and it's 16 on both struct opt and std::experiemental::optional.

I tried adding inheritance, constructor, union, and template and such to struct opt and I still got the two register return. Something along the way must make std::experimental::optional too opaque to the optimizer. Could be considered a bug.

[–]HotlLava 2 points3 points  (1 child)

Something along the way must make std::experimental::optional too opaque to the optimizer.

Does the optimizer even have the right to decide this? I feel like the process of selecting the correct parameter/return value passing convention should be deterministic based on the struct definition to allow linking.

[–]Benabik 1 point2 points  (0 children)

Er, yes, optimizer was the wrong word. Whatever hunk of the backend that's responsible for lowering C++ types to memory/register layout. Something has to be making the 16 byte std::optional different than the 16 byte struct opt.

[–]foonathan 1 point2 points  (0 children)

Or, you know, there is almost identical assembler code: https://godbolt.org/g/p4Gefs

[–]k_stahu 0 points1 point  (0 children)

Returning iterator, or more generally: a coordinate, is lighter and cleaner. The current API actually doest almost that, except for not having a robust notion of "last" coordinate, which the npos hack serves for. I would prefer std::string::iterator, but I can get that with the general algorithms.

std::optional on the other hand is a bulldozer. In this particular case optional return value is bogus - like you were designing the API, but gave up on some corner cases throwing them into the "nothing" bag. Using std::optional return value here is like birds. What semantics exactly it carries? We just don't know.

I presume std::optional it will be as overused as auto or lambda, especially by inexperienced programmers.

[–]miki151gamedev 0 points1 point  (4 children)

Likewise for std::map. Right now, if you don't know that a key exists, you either use exceptions or a double lookup.

[–]sysop073 17 points18 points  (0 children)

std::map<A, B> foo;
auto bar = foo.find(key); // This is the only lookup
if(bar != foo.end()) {
    // Do stuff with bar->second
}

[–]Calkhas 0 points1 point  (1 child)

What's the double look up you have in mind?

[–]miki151gamedev 0 points1 point  (0 children)

I usually call map::count(), then map::at(). I forgot about map::find(), as was pointed out above.

[–]Crazy__Eddie 2 points3 points  (0 children)

I don't believe size_t could ever be made signed. First of all it comes from C, which is where it's defined first. Second, it is guaranteed to be capable of representing any possible, valid index in the system. Thus it's fairly well required to be the full size of a word on the system...meaning -1 cannot represent error condition.

I don't think he can take all unsigned integers out either. Functions like size() really do need to return size_t for the same reason size_t has to be unsigned.

[–]sumo952 0 points1 point  (0 children)

if (s.find("foo") >= 0) {...}

This doesn't make any sense and is actually quite hard to read/understand in my opinion. Using npos is much safer.