0

Are there any other languages that use the new C++11 uniform initialization syntax thoroughly throughout the entire language, or is this entirely a C++ idiom at this point?

I tried googling and thinking of any off the top of my head and couldn't really find any.

For those not familiar with C++11:

Basically, your typical initialization in C++ used to be an assignment/copy initializer similar to other languages:

int x = 5;

Now, the new syntax that is recommended for use is:

int x{5};

This is called the new uniform initialization syntax.

11
  • 3
    Its been awhile since I've touched C++... but even after reading the FAQ through a few times I can't even figure out what it does or should do to see if it has any similarity to other languages that I am familiar with. Could you elaborate on how this functionality works for those who are less familiar with C++11?
    – user40980
    Commented Nov 3, 2014 at 19:45
  • 3
    That specific syntax, braces and all? That seems awfully specific and useless. Any motivation, aside from curiosity?
    – user7043
    Commented Nov 3, 2014 at 20:06
  • 7
    Personally, I dislike its use for initializing scalars. int x = 5; is clear, unambiguous, and has been used successfully for decades. Commented Nov 3, 2014 at 22:19
  • 2
    @Robert - If its C++ and its not broken.... break it....
    – mattnz
    Commented Nov 3, 2014 at 22:53
  • 2
    @delnan: Uniformity has its place for writing very generic code; the "most vexing parse" can cause issues; backward syntax compatibility. Commented Nov 4, 2014 at 2:15

2 Answers 2

6

Uniform initialization syntax seems geared to aggregate types. Looking at the FAQ, UIS's purpose is to solve a variety of loosely (or subtly?) related problems (ambiguities and impossibilities), some of which don't exist or have other solutions in other languages. Consequently, I don't think you'll find anything exactly the same in other languages; you may find similar syntax, but less so the semantics.

The problems that UIS addresses include:

  1. initializing aggregate types that don't have constructors (including arrays) outside of definition statements (such as in member initialization lists). UIS addresses this by expanding the initializer list feature (in a sense, UIS is an expansion of initializer lists), which exists in other language. Robert Harvey's answer is one example. Initializer lists also exist in previous versions of C++ and C, going back to at least C99

  2. ambiguous declarations. The following could be a function declaration or variable definition, depending on context:

     int b(foo);
    

This isn't a common issue in other languages.

  1. need to repeat class in constructor name, when it could be inferred from context:

     Complex c = {1,1};
     Complex conjugate(const Complex& c) {
         return {c.real(), -c.imaginary()};
     }
     Complex d = conjugate({1,1});
    

Other languages generally approach the first from another angle, using type inference:

    var c = Complex(1,1);

Other languages also use type conversion to cover some instances of the other two cases (function argument and return type inference). UIS is more general in that it supports converting a bunch of values, where most languages only support converting a single value (i.e. multiple parameter constructors can participate in conversions in C++, where usually only unary converters are supported in other languages; however, see "Implicit Conversions" under Scala for a workaround). 4. addressing the above (which add bare "{...}" expressions to C++) adds a new issue: constructor argument list delimiters aren't consistent with UIS. Allowing constructor argument lists to use brackets instead of parentheses makes various other construction expressions consistent with implicit constructor names:

    Foo f{x};   // direct initialization
    new Foo{x}; // explicit constructor name
    z = Foo{x}; // cast
    Foo{x};     // temporary

Scala#

Scala has a similar syntax (closer to C#'s than C++'s) for instance creation expressions that covers the initializer-list use-case:

class Foo(var bar:String=null) {}
var foo = new Foo() {bar="Bar"}

However, the semantics are different. Rather than simply initializing object fields, the bracketed expression after the constructor creates a new anonymous class that's a child of the named class, which is then instantiated. The constructor for the anonymous class includes the initializations in the brackets. The variable's type is the anonymous class, rather than the named class.

Since a new class is defined, it can also include just about any statement allowed in a class definition, such as field and method definitions.

class Foo(var bar:String=null) {}
var foo = new Foo() {bar="Bar"; var baz="Baz", def qux()=bar+baz}

Unlike C++ and like C#, the equals symbol is required.

Something like the example definition in the question is valid in Scala, but it has different semantics from anything previously mentioned:

var i = {5}

In this case, the bracketed expression is simply a compound expression and equivalent to the following C++ statement:

int i = (5);

Implicit Conversions

Conversions in Scala are handled with views and implicit classes. When coupled with tuples, these can be used to convert multiple values to a single type without specifying the type, similar to UIS when the list contains constructor arguments and no class name is given. Like UIS, the type can be inferred from context. Unlike UIS, parentheses are used rather than brackets (as tuples are delimited with parentheses).

Note that Scala has another kind of implicit thing, the implicit parameter, which makes an appearance below in sign. Rather than being used when implicitly converting values, it's used to implicitly provide arguments to a function (i.e. when an argument isn't explicitly passed to a function, a value is automatically created and passed). The use of the word "implicit" for different things isn't a naming collision; all the implicit things work together to extend behavior (more on this below).

Implicit class:

// note: sign[T](n)(num) is a curried function
def sign[T : Numeric](x:T)(implicit num:Numeric[T]) = num.compare(x, num.zero)

implicit class Complex(ri:(Double,Double)) { 
  val real = ri._1
  val imaginary = ri._2
  def conjugate = new Complex((real, -imaginary))
}
// could also be methods of Complex
def abs(c:Complex) = Math.sqrt(c.real * c.real + c.imaginary * c.imaginary) 
def sqrt(c:Complex) = {
  val real = Math.sqrt((abs(c)+c.real)/2)
  val imaginary = sign(c.imaginary) * Math.sqrt((abs(c)-c.real)/2)
  new Complex((real, imaginary))
}

val c:Complex = (1.0,2.0)
sqrt((3.0,4.0))
// Conversions can even be used for method calls, which I don't think UISs can do
(1.0,2.0).conjugate

View:

def sign[T : Numeric](n:T)(implicit num:Numeric[T]) = num.compare(n, num.zero)

class Complex(val real:Double, val imaginary:Double) {
  def conjugate = new Complex(real, -imaginary)
}
implicit def complexify(ab:(Double, Double)) = new Complex(ab._1, ab._2)

def abs(c:Complex) = Math.sqrt(c.real * c.real + c.imaginary * c.imaginary) 
def sqrt(c:Complex) = {
  val real = Math.sqrt((abs(c)+c.real)/2)
  val imaginary = sign(c.imaginary) * Math.sqrt((abs(c)-c.real)/2)
  new Complex(real, imaginary)
}

val c:Complex = (1.0,2.0)
sqrt((3.0,4.0))
(1.0,2.0).conjugate

Aside: as mentioned above, implicits in Scala are used to extend behavior. Implicit conversions for method calls ((1.0,2.0).conjugate in the examples) are usually used as an indirect way to add methods to an existing class by using the adapter pattern. Other languages (such as C#, Objective-C, Ruby and Python) take another approach and have open classes, allowing new methods to be added directly. For examples, consider C#'s extension methods, F#'s type extensions, Objective-C's categories and Haskell's type classes (which also addresses C++ overloading and template concepts). Scala's implicit parameters and objects can also be used like Haskell's type classes (instead of the adapter pattern you get with implicit conversions) basically the way you create traits classes in C++. For more on this topic:

Looking at things like sign and (1.0,2.0).conjugate, I can't help but feel that Scala is weird and wonderful.

Erlang#

Erlang's record initializers use squiggly brackets and key-value pairs. The record name must be given (prefixed with "#"). Fields with default values can be left out of the initializer.

-record(foo, {bar, baz="Baz"}).
Foo = #foo{bar="Bar"}.

During compilation, records are converted to tuples, losing the field names.

Go#

In Go, composite literals can be used to initialize objects. Like similar features in other languages, brackets are used as delimiters. Unlike UIS (but like other languages), the type must be included. Within the brackets is an element list. An element can itself be a composite literal. Lists can be keyed or unkeyed (keys give struct field names, array indices or map keys); keys must be present or absent for all elements in a list (this restriction doesn't apply across separate lists within a literal). If keys are absent, all fields must be given values.

type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }

// 3 different lists, 1 keyed and 2 unkeyed:
ln0 := Line{Point3D{x: 1}, Point3D{0,1,1}};
// array length could also be given as "...":
ns := [3]int32{0,1,2}
// strs[2] is given zero value for strings:
strs := [...]string{0:"", 1:"I", 3: "III"}

Haskell#

Haskell types can be declared with a record syntax, which are very similar to Erlang's records. Record constructors can take a initializers. Record syntax also declares accessor functions with the same name as the fields, and record fields can be updated using the same syntax as initialization. As far as I know, default values cannot be provided for fields, so all fields must be present in the initializer (though not when updating), though they can be given in any order.

data Foo = Foo {bar::String, baz::String}
foo = Foo {baz="Baz", bar="Bar"}
bar foo # "Bar"
# update by name
qux = foo {bar="qux"}

Similar to Erlang, records are converted to plain types; the above results in:

data Foo = Foo String String
bar (Foo bar baz) = bar
baz (Foo bar baz) = baz

(except that this doesn't support initializers or updates-by-name.)

Perl 5 & Javascript#

Perl 5 and Javascript don't have language level support for initializer lists, but constructors can take hashes (Perl 5) or objects (Javascript), which they can use to set object fields. Coincidentally, JS object literals and Perl hash refs use squiggly brackets (though Perl doesn't need to use hash refs to pass a hash to a constructor).

Perl 5:

package Foo;

sub new {
    my $class = shift;
    my $self = {@_};
    return bless $self, $class;
}

package main;

my $foo = new Foo(bar=>"Bar");

JS:

function Foo(obj) {
    for (var p in obj) {
        this[p] = obj[p];
    }
}
var foo = new Foo({bar: "Bar"})

Perl 6#

As with Perl 5, a hash can be passed to constructors to set attributes. Unlike Perl 5, Mu, the Perl 6 root class, has a constructor which takes a hash and defines attributes & accessors, so this doesn't need to be explicitly implemented.

class Foo {
    has $.bar as rw;
}
my $foo = Foo.new(bar=>"Bar");

Powershell#

Powershell's new-object command supports initialization, property addition and method invocation using the -Property argument:

New-Object PSObject -Property @{
    foo = "bar"
}
3
  • C# can also make use of anonymous classes. See the second and fourth examples in my answer. Commented Nov 3, 2014 at 22:21
  • @RobertHarvey: My point isn't that Scala has unique features but that there's something syntactically similar in Scala, but it isn't an initializer (something similar could be said for C#'s anonymous classes; is that what your comment means?). In your two examples, aren't the anonymous classes syntactically and semantically different from the initializers? Syntactically, the anonymous class takes the place of the constructor call; semantically, initializers don't involve the creation of an anonymous class. In Scala, the anonymous class is in addition to the constructor call.
    – outis
    Commented Nov 3, 2014 at 23:04
  • The problem with UIS is that it introduces new ambiguity: std::vector<std::string>(1) and std::vector<std::string>{1} means the same, while std::vector<int>(1) and std::vector<int>{1} are different. For this reason, it is more confusing than helpful, and I think it is of negative value introducing it to c++11.
    – Siyuan Ren
    Commented Nov 4, 2014 at 8:53
6

C# has something similar; they're called Object/Collection Initializers. They have several, but similar, forms:

// Object initialization
var cat = new Cat { Age = 10, Name = "Fluffy" };

// Object initialization with anonymous type
var pet = new { Age = 10, Name = "Fluffy" };

// Collection initialization
var digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Collection initialization with Linq and anonymous type
var productInfos =
    from p in products
    select new { p.ProductName, p.UnitPrice };

Note that var in C# has the same function as auto in C++.

Also note that the form:

int x{5}

is really just the "scalar" form of a collection initializer (it is a single element, potentially belonging to some collection).

Not the answer you're looking for? Browse other questions tagged or ask your own question.