Showing posts with label cpp11_initialize_list. Show all posts
Showing posts with label cpp11_initialize_list. Show all posts

Jul 25, 2022

[C++] using private type as tag technique

Reference:
https://devblogs.microsoft.com/oldnewthing/20210719-00/?p=105454
https://devblogs.microsoft.com/oldnewthing/20220721-00/?p=106879

#include <cctype>
#include <iostream>
#include <memory>

using namespace std;

class Fun {
private:
  struct private_constructor {
    // mark as explicit otherwise caller can abuse with init. private_constructor with '{}'
    explicit private_constructor() = default;
  };

public:
  Fun(int data, private_constructor) {}

  static Fun create_Fun(int data) { return Fun(data, private_constructor()); }
};

int main() {
  //
  //
  Fun myFun = Fun::create_Fun(42);
}

Jul 10, 2019

[c++] initializer_list as r-value provides looping source

Nicolai Josuttis shared a trick about using std::ref + std::initializer_list:
https://twitter.com/NicoJosuttis/status/1148659818770640896

Back in the old days @VMW we have vmw::ref as reference counter type for our objects, here, std::ref is a free function returns std::reference_wrapper which ref-init the passing in object.

Since C++14, the underline implement of std::initializer_list has been standardized, states that:
The underlying array is a temporary array of type const T[N], in which each element is copy-initialized.

Modified sample code address this in more detailed phase:
#include <iostream>
#include <functional>

using namespace std;

struct Fun{
    int data = 42;
    Fun() = default;
    Fun(const Fun&) = delete;
};


int main(){
    Fun f1;
    Fun f2;
    for(auto& a : {ref(f1), ref(f2)}){
        cout << a.get().data << endl;
    }

    for(auto& a : {Fun{}, Fun{}}){
        cout << a.data << endl;
    }
}

Aug 20, 2015

[C++11/14] constant expression value can convert to smaller size data structure

#include <initializer_list>
#include <iostream>

using namespace std;

struct Test{
    Test(initializer_list<char> list){
        cout << "IL constructor" << endl;
    }

    Test(int a, char c){
        cout << "general constructor" << endl;
    }
};


template<char c, int i>
void fun(){
    /*
        8.5.4/7:
        A narrowing conversion is an implicit conversion […] from an integer
        type or unscoped enumeration type to an integer type that cannot
        represent all the values of the original type, except where the source
        is a constant expression and the actual value after conversion will
        fit into the target type and will produce the original value when
        converted back to the original type.
    */
    Test{i, c};  // print: IL constructor
}

int main(){
    fun<'^', 3>();
    Test('^', 4);  // print: general constructor
}

Aug 4, 2014

[C++11] Guideline for lots of things XD

Reference: Beware of C++ - Nicolai Josuttus


Guideline for explicit constructor
  • The default constructor should never be explicit
    • If all arguments of an explicit constructor have default values, declare the default constructor separately
  • An initializer list constructor should never be explicit
    • otherwise, empty initializer list could match to default argument constructor:
      Constructor(int=0){};
  • Any other constructor should be explicit,
    if
    • parameters affect behavior instead of core content
  • Shouldn't the default constructor always be its own beast?
Guidelines for constexpr:

  • constexpr is not for optimization. The compilers can inline well already.
  • use constexpr when guaranteed static initialization is important
    e.g
    the construction of global atomics really cannot be deferred to run time.
  • use constexpr when you anticipate using the results to define array sizes or appear within template non-type arguments
  • "Making everything possible constexpr" is borderline insane. It leads to unnecessarily increased compile times, potential code bloat, and wishes to overload on constexpr so that we can select different algorithms for compile time and run time.
  • by all means "be generous", but use constexpr only when there is a potential need for guaranteed compile-time evaluation.
  • beneficial uses of constexpr on non-trivial computations aren't always obvious from past experience.

Guidelines for template parameters:

  • If knowing the object is always cheap to copy then pass by value.
  • If it might not be cheap to copy, making a choice:
    • if the expected type is likely to be an r-value and is moveable, then call by value so that the caller passes temporaries or uses move.
    • if it's not cheap to copy and not moveable, then still take by value and let the caller use std::ref()
    • otherwise use const l-value reference
      • think about whether and where to decay
  • If returning s.t in the argument, use a non-const l-value reference
  • If having to pass move semantics into other parts of the called function, declare as universal reference and forward<>
    • think about whether and where to decay

[C++11] empty initializer_list

{} will be matched to Fun(int=1) i.e ,
Fun tmp = {};
Code:
#include <initializer_list>

using namespace std;

struct Fun
{
    Fun(int=1)
    {
    }

    explicit Fun(initializer_list<int>)
    {
    }
};

void fun(const Fun&)
{

}

int main()
{
    fun({});
}

Apr 1, 2014

[c++11] Zero Initialisation for Classes

Devils are in the details :
  • With default constructor, it will initialize the integrals iff it's called by new T(); 
  • If it's user defined constructor, but without explicitly initialize integrals, even called by new T();
    the integrals will not be initialized as integrals();
  • If it's been called by new T;
    no matter user define or default constructor, non of the integrals will be initialized.
Zero Initialisation for Classes
default initialization
zero initialization
value initialization


#include <iostream>
using namespace std;

class Base
{
public:
Base(){cout << "ha" << endl;}
virtual ~ Base(){cout << "base destroctor" << endl;}
};

class cInitialisationReporter : public Base
{
  int i;
public:
  virtual ~cInitialisationReporter()
  {
      std::cout << "cInitialisationReporter::i is " << i << '\n';
  }
};


class cInitialisationReporter2: public Base
{
  int i;
public:
  cInitialisationReporter2() {}
  virtual ~cInitialisationReporter2()
  {
      std::cout << "cInitialisationReporter2::i is " << i << '\n';
  }
};


class cInitialisationReporter3: public Base
{
  int i;
public:
  cInitialisationReporter3()=default;
  virtual ~cInitialisationReporter3()
  {
      std::cout << "cInitialisationReporter3::i is " << i << '\n';
  }
};

template <class T> void
SetMemAndPlacementConstruct_ZeroInitialisation()
{
  T* allocated = static_cast<T*>(malloc(sizeof(T)));
  signed char* asCharPtr = reinterpret_cast<signed char*>(allocated);
  for(int i = 0; i != sizeof(T); ++i)
  {
      asCharPtr[i] = -1;
  }
  new((void*)allocated) T();
  allocated->~T();
}


template <class T> void
SetMemAndPlacementConstruct_DefaultInitialisation()
{
  T* allocated = static_cast<T*>(malloc(sizeof(T)));
  signed char* asCharPtr = reinterpret_cast<signed char*>(allocated);
  for(int i = 0; i != sizeof(T); ++i)
  {
      asCharPtr[i] = -1;
  }
  new((void*)allocated) T;
  allocated->~T();
}

int
main(int argc, char* argv[])
{
  SetMemAndPlacementConstruct_ZeroInitialisation<cInitialisationReporter>();
  SetMemAndPlacementConstruct_ZeroInitialisation<cInitialisationReporter2>();
  SetMemAndPlacementConstruct_ZeroInitialisation<cInitialisationReporter3>();
  SetMemAndPlacementConstruct_DefaultInitialisation<cInitialisationReporter>();
  SetMemAndPlacementConstruct_DefaultInitialisation<cInitialisationReporter2>();
  SetMemAndPlacementConstruct_DefaultInitialisation<cInitialisationReporter3>();
  return 0;
}
Output:
clang++ -std=c++1y -O3 main.cpp && ./a.out
ha
cInitialisationReporter::i is 0
base destroctor
ha
cInitialisationReporter2::i is -1
base destroctor
ha
cInitialisationReporter3::i is 0
base destroctor
ha
cInitialisationReporter::i is -1
base destroctor
ha
cInitialisationReporter2::i is -1
base destroctor
ha
cInitialisationReporter3::i is -1
base destroctor

Dec 4, 2013

[C++11][NOTE][BEGINNER] initilizer_list

According to §8.5.4.3 in the C++ standard:
"List-initialization of an object or reference of type T is defined as follows:
— If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
— Otherwise, if T is an aggregate, aggregate initialization is performed (§8.5.1).
— Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (§8.5).
— Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (§13.3§13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
— (more cases...)"
a1 is default initialized, as described in §8.5.0.11
a2 doesn't actually use the initializer_list constructor with a list of zero elements, but the default constructor, as described by the first option of the list above.
a3's and a4's constructor is chosen in overload resolution, as described in §13.3.1.7:
"When objects of non-aggregate class type T are list-initialized (§8.5.4), overload resolution selects the constructor in two phases:
— Initially, the candidate functions are the initializer-list constructors (§8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list."
Initializer list constructors are greedy, so even though A(int) constructor is available, the standard mandates that initializer_list<int> is prioritized, and if - and only if - it's not available, the compiler is allowed to look for other constructors. (This is why it is not recommended to provide a constructor that ambiguously overloads with an initializer_list constructor. See the answer to #4 in http://herbsutter.com/2013/05/09/gotw-1-solution/ )
 
#include <initializer_list>
#include <iostream>

struct A {
  A() { std::cout << "1"; }

  A(int) { std::cout << "2"; }

  A(std::initializer_list<int>) { std::cout << "3"; }
};

int main(int argc, char *argv[]) {
  A a1;
  A a2{};
  A a3{ 1 };
  A a4{ 1, 2 };
} // 1133