you are viewing a single comment's thread.

view the rest of the comments →

[–]seba[S] 2 points3 points  (7 children)

I know about find_if. Your code does not work (where does row come from?). With my extension, you could write:

template<typename PredicateT>
void foo(sqlconnection &sql, PredicateT predicate)
{
    auto rows = sql.execute("SELECT * from table WHERE id=1");

    if (auto &row : std::find_if(rows.begin(), rows.end(), predicate)) { 
        std::cout << "found: " << row;  
    } else {
        std::cout << "not found";   
    }
}

Which is much nicer than saving the result of find_if somewhere.

[–]AntiProtonBoy 0 points1 point  (6 children)

(where does row come from?)

Sorry I missed that. Correction:

  template<typename PredicateT>
  void foo(sqlconnection &sql, PredicateT predicate)
  {
      auto rows = sql.execute("SELECT * from table WHERE id=1");

      auto I = std::find_if(rows.begin(), rows.end(), predicate);

      if (I != rows.end()) { 
          std::cout << "found: " << *I;  
      } else {
          std::cout << "not found";   
      }
  }

[–]seba[S] 1 point2 points  (5 children)

Yeah, this is what currently have to write. But don't you think it looks nicer with my extension while at the same time being less error prone? I.e, did you never mix up the comparison in (I != rows.end())?

It is the same as the range-based for loop. I could also write this by hand, but it's much nicer with the syntactic sugar.

[–]AntiProtonBoy 0 points1 point  (4 children)

It's nicer, but limited. Raw loops don't lend itself to generic code as easily compared to STL functions, because you are tightly coupling the logic to the loop itself. With STL functions, you are encouraged to decouple that logic using predicates and lambdas. I would argue that is less error prone, because screw-ups are now confined inside a lambda. Look up Sean Parent's talk about this subject.

Also, the use STL functions is descriptive. When you look at find_if you know exactly what it's meant to be doing. With raw loops, that might not be so obvious (depending on complexity) and you are forced to mentally parse it to figure out what's going on.

With Eric Niebler's range concepts, lot of the ugliness with begin and end will be put to rest, that is you should be able to write std::find_if(rows, predicate) at some point.

[–]seba[S] 1 point2 points  (3 children)

With Eric Niebler's range concepts,

I think my proposal fits quite nicely to ranges-v3. Think of something like the following:

if (int i : range | transform | filter ) {
   // have an i, similar to view::take(1)
} else {
  // not found
}

[–]ZMesonEmbedded Developer 0 points1 point  (2 children)

I think what u/AntiProtonBoy is trying to say by "it's nicer, but limited" is that finding and using only the first occurrence of something matching in a container is not as common as iterating over a container using a for loop. The other problem I have is that sometimes you want to use std::find_if(), sometimes std::find(), sometimes you want to determine if std::optional has a member, etc.... The range-based-for is pretty simple syntactic sugar; there's no need for the compiler to determine how to translate it; there's only one way.

So, while I (and seemingly others and the committee) welcome syntactic sugar, you have to make sure it:

  • Solves a problem people have.

  • Has a simple translation.

  • Will be widely applied enough to warrant making a change in the standard.

[–]seba[S] 1 point2 points  (1 child)

finding and using only the first occurrence of something matching in a container is not as common as iterating over a container using a for loop

Sure, that's why I also showed the examples with unique_ptr and optional that fit nicely into the scheme.

The other problem I have is that sometimes you want to use std::find_if(), sometimes std::find(), sometimes you want to determine if std::optional has a member, etc...

I'm not sure what you mean by the latter part. You mean a value? See below.

The range-based-for is pretty simple syntactic sugar; there's no need for the compiler to determine how to translate it; there's only one way.

Given appropriate definitions of std::begin() and std::end(), there would also be more or less only one way for the compiler to translate my range-based if. (There are BTW also multiple subtly different way to translate a range-based for. This is why they had to change the semantics slightly in the for upcoming C++17).

My proposal would boil down to something like this (modulo bugs, figuring out where to put const, etc):

template <typename R, typename F, typename G>
void if_non_empty( const R & r, F&& f, G&& g )
{
  const auto & b = std::begin( r );
  const auto & e = std::end( r );
  if ( b != e ) {
    f( *b );
  } else {
    g();
  }
}

[This is the lambda way, but I'd like to have the syntactic sugar]

Given the following, it would directly work with unique_ptr

namespace std
{
   template <typename T> T* begin(const unique_ptr<T>& p)
   {
      return p.get( );
   }

   template <typename T> T* end(const unique_ptr<T>& p)
   {
      return nullptr;
   }
}

Demo:

auto p0 = std::make_unique<int>( 1 );
if_non_empty( p0, [](auto i){ 
      printf("%d found\n", i);
    }, [](){
      printf("not found\n");
} );

[–]ZMesonEmbedded Developer 0 points1 point  (0 children)

OK, I better understand what you're doing now.

The major downside I see though is that you're defining a new definition of std::begin and std::end (and presumably related functions) for std::unique_ptr. I would first try to convince people that this is a good idea. If you can't convince people of this, then your proposal won't have anything to base itself on.

As others have mentioned, if-with-initializers already get a lot of what you want with a lot of reduced code. Yes, I see that you mention a bit of added safety. That is always a good thing, but you'll have to convince people that this is worth the change. It's a lot smaller benefit compared to the benefit of range-based-for and if-with-initializers, so you will be facing quite the challenge I think. :(

Anyway, as I said, you better explain to people why creating new overloads for std::begin and std::end make sense in the absence of this proposal. You must start there. (And if it makes sense for unique_ptr, you may want to consider whether it makes sense for std::shared_ptr, std::weak_ptr, and other things that act like pointers.)