Skip to content

[SUGGESTION] Confusing std::vector ctor calls in Cpp1 and Cpp2 #193

Closed

Description

@wolfseifert

In Cpp1 we have the confusing fact that

#include <vector>
using namespace ::std;
int main() {
  auto a = vector<int>{5, 1}; // vector of five and one
  auto b = vector<int>(5, 1); // vector of five ones
}

Experimenting with (pure) Cpp2 I got a similar case

main: () -> int = {
  a: std::vector<int> = (5, 1); // vector of five and one
  b := std::vector<int>(5, 1); // vector of five ones
}

This becomes clear when looking at the tanspiled code

  std::vector<int> a { 5, 1 };  // vector of five and one
  auto b { std::vector<int>(5, 1) }; // vector of five ones

For me the Cpp2 case looks even more confusing (than the Cpp1 case), because both ctor calls use parenthesis.

Activity

  1. MichaelCook commented on Jan 2, 2023

    @MichaelCook

    Perhaps another way to state this issue is: Should cpp2 do fine-grained removal/repair of unfortunate cpp1 API choices?

  2. robertshepherdcpp commented on Jan 2, 2023

    @robertshepherdcpp

    What about if we had a literal for the size, like _s so then we could just define the vector like:

    a: std::vector<int> = (5, 1_s);  // a vector of 5 ones
    b: std::vector<int> = (5, 1);     // a vector of 5 and 1

    and then we could do it like this:

      b := std::vector<int>(5, 1_s); // vector of five ones
  3. JohelEGP commented on Jan 2, 2023

    @JohelEGP
    Contributor

    How would you combine that with existing suffixes like ULL?

  4. filipsajdak commented on Jan 2, 2023

    @filipsajdak
    Contributor

    Soon you will be able to write:

    a: std::vector<int> = (5, 1 as std::size_t);
  5. robertshepherdcpp commented on Jan 2, 2023

    @robertshepherdcpp

    @filipsajdak Yes, thats good. Would an overload for a std::size_t at the end of the vector be possible?

  6. fpelliccioni commented on Jan 2, 2023

    @fpelliccioni
    Contributor

    @fpelliccioni Yes, thats good. Would an overload for a std::size_t at the end of the vector be possible?

    I think you confused me with @filipsajdak

  7. JohelEGP commented on Jan 2, 2023

    @JohelEGP
    Contributor
    a: std::vector<int> = (5, 1 as std::size_t);

    What about

      auto a = vector<std::size_t>{5z, 1z}; // vector of five and one
      auto b = vector<std::size_t>(5z, 1z); // vector of five ones
  8. filipsajdak commented on Jan 2, 2023

    @filipsajdak
    Contributor

    @JohelEGP cheater. 🙂

    I agree that there is an issue. I am just trying to advertise the cpp2 casting feature.

    What we want to achieve is unambiguous syntax. The spotted issue sure will need some reasonable solution.

  9. switch-blade-stuff commented on Jan 2, 2023

    @switch-blade-stuff

    IMO this would be fairly difficult to solve while keeping full compatibility with Cpp1, the issue is that in C++, the aggregate (i.e. {}) constructor favors initializer lists, while the "classic" parentheses never generate initializer lists.

    So, this would either require a change in constructors of std::vector (and other containers too) to outright remove the size & value constructor, or use some kind of tag like with std::in_place_t to disambiguate overloads; or to forego the initializer-list/aggregate constructor form in Cpp2, which would prevent the ambiguity, but also disable initializer lists.

  10. switch-blade-stuff commented on Jan 2, 2023

    @switch-blade-stuff

    Honestly, it would be easier if C++ just left {} for initializer lists, and only use () for constructors. That way there would be no ambiguity, if you saw std::vector v = {0, 1}, you'd know it is definitely an initializer list, and the vector contains 0 and 1, instead of 1 zero.

  11. marioarbras commented on Jan 2, 2023

    @marioarbras

    Honestly, it would be easier if C++ just left {} for initializer lists, and only use () for constructors. That way there would be no ambiguity, if you saw std::vector v = {0, 1}, you'd know it is definitely an initializer list, and the vector contains 0 and 1, instead of 1 zero.

    This would be really nice. An alternative idea could be to use an explicit cast for initializer lists.

    v: std::vector = (5, 1) as std::initializer_list;
    
  12. robertshepherdcpp commented on Jan 2, 2023

    @robertshepherdcpp

    @marioarbras Or we could have it the other way round, depending on which is more common: initializing a vector with a std::initializer_list like:

      a: std::vector<int> = (5, 1); // vector of five and one

    or initializing it with a certain amount of elements like:

      b := std::vector<int>(5, 1); // vector of five ones

    Which would be the more common usage? Personally I use the initializer list more.

  13. hsutter commented on Jan 2, 2023

    @hsutter
    Owner

    Yes, fixing that problem is on my list to address as follows (I just haven't implemented it yet):

    v1: vector<int> = (5, 1);   // size == 5, same as "= ((1, 1, 1, 1, 1))"
    v2: vector<int> = ((5, 1)); // size == 2
    

    The idea is that the parens in v2's initializer make it explicit that we are constructing with a single argument that is a list. (Do you "see" it visually, that the outer parens now clearly contain only a single thing? I think it's visually clear, but I'm curious to learn whether others will see it that way too.)

  14. switch-blade-stuff commented on Jan 2, 2023

    @switch-blade-stuff

    v: std::vector = (5, 1) as std::initializer_list;

    Honestly, to me that looks just slightly better than manual insertion 😅

    I suppose we could just explicitly use braces for initializer lists only, where if {stuff} is encountered in a constructor it would be replaced by ({stuff}), forcing the initializer-list overload.

    Then again, this would fall flat for aggregate types such as std::array, as well as the "classic" C-style arrays that do not use std::initializer_list.

  15. 33 remaining items

  16. wolfseifert commented on Jan 4, 2023

    @wolfseifert
    Author

    So the only way to pass a vector of 5 and 1 to a function print is like this:

    a: std::vector = (5, 1);
    print(a); // prints 5 1
    

    Neither

    print(std::vector(5, 1));  // prints 1 1 1 1 1
    

    nor

    print(: std::vector = (5, 1));  // does not transpile
    

    do the trick. Or did I miss something?

  17. jcanizales commented on Jan 6, 2023

    @jcanizales

    @wolfseifert does print((5, 1)) work?

  18. wolfseifert commented on Jan 7, 2023

    @wolfseifert
    Author

    No, it does not. I will open a new issue for this later.

  19. hsutter commented on Jan 7, 2023

    @hsutter
    Owner

    @wolfseifert:

    print(: std::vector = (5, 1));  // does not transpile
    

    I've been meaning to allow unnamed objects eventually, and seeing you actually trying to write the code pushed me over the edge to implement it now. :)

    This now works:

    print: (x:_) = {
        for x do :(elem:_) = {
            std::cout << elem << " ";
        }
    }
    
    main: ()->int = {
        print( : std::vector = (5,1) );   // prints "5 1"
    }
    
  20. msadeqhe commented on Jan 8, 2023

    @msadeqhe

    Is it possible to infer type from function declaration?

    print: (x: std::vector<int>) = {
        for x do : (elem: _) = {
            std::cout << elem << " ";
        }
    }
    
    main: () ->int = {
        print(: = (5, 1));   // Here
        print(: _ = (5, 1)); // Or like this
    }
    

    ... and from variables declaration:

    x :std::vector<int> = (5, 1);
    y :std::vector<int> = : std::vector = (5, 1);
    z :std::vector<int> = : = (5, 1); // Here
    a :std::vector<int> = : _ = (5, 1); // Or like this
    
  21. wolfseifert commented on Jan 8, 2023

    @wolfseifert
    Author

    How I understand this so far:

    • x: std::vector = (5, 1);
      is the current "standard" way of initializing a vector. It is transpiled to
      std::vector x {5, 1};
    • y: std::vector = : std::vector = (5, 1);
      is a more cumbersome way of achieving the same thing. It is transpiled to
      std::vector y {std::vector{5, 1}};
      This variant however makes a lot of sense for something like
      y: std::vector = : myvector = (5, 1);
      where myvector is a subclass of vector but y must be of type vector.
    • z: std::vector = := (5, 1);
      is transpiled to
      std::vector z {auto{5, 1}}
      which does not C++ compile.
  22. filipsajdak commented on Jan 8, 2023

    @filipsajdak
    Contributor

    What do you think about that:

    print( (5,1) as std::vector ); // for generic functions; prints "5 1"
    
    taking_vector( forward (5,1) ); // assuming that this function expects a vector and 
                                    // that we can deduce the type from the function declaration

    For the std::vector "bad API" issue, maybe we should think about correcting it on the cppfront side e.g., like that:

    main: () -> int = {
        print( std::vector<int>().create(5, 1) ); // prints 1 1 1 1 1
        // or ...
        print( create<std::vector>(5, 1) ); // prints 1 1 1 1 1
    }
    
    template <typename X>
    auto create(std::vector<X>&&, std::size_t size, X&& value = X{}) -> decltype(auto)
    {
        return std::vector<X>(size, value);
    }
    
    template <template <typename...> class V, typename X>
    auto create(std::size_t size, X&& value = X{}) -> decltype(auto)
    {
        return V<X>(size, value);
    }
    
    print: (x:_) = {
        for x do :(elem :_) = {
            std::cout << elem << " ";
        }
        std::cout << std::endl;
    }
    
    #include <vector>

    Names can be adjusted (resize cannot be used instead of create as vector already has a resize method, and UFCS prefers methods over free functions).

  23. filipsajdak commented on Jan 8, 2023

    @filipsajdak
    Contributor

    I have just realized that syntax for unnamed objects

    : std::vector = (5, 1)

    Follows the same syntax as unnamed functions (aka lambdas):

    : (i:int) -> int = { return i+1; }
  24. hsutter commented on Jan 8, 2023

    @hsutter
    Owner

    Re syntax: Yes, in both cases it's the identical syntax as a named object/function, but without the name.

    I think we've bottomed out on this for now. Thanks, everyone!

  25. JohelEGP commented on May 18, 2023

    @JohelEGP
    Contributor

    #284 (comment) means it's possible to call std::vector<int>'s (int, int) constructor: https://cpp2.godbolt.org/z/Pvj8fj1he.

    using std::vector;
    main: () = {
      z := 0.vector(0);
    }
    
  26. AbhinavK00 commented on May 19, 2023

    @AbhinavK00

    That's a nice hack.
    But the fact that it took this long to come up with a solution shows we need some kind of change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions