4 Eylül 2019 Çarşamba

std::index_sequence

Giriş
std::integer_sequence ile kardeştir.

Parametre Olarak Geçmek
Örnek
Template koduna şöyle yazarız
template<std::size_t... Is>
void foo(std::index_sequence<Is...>) {
  ...
}
Örnek
Elimizde şöyle bir kod olsun.
namespace detail
{
  template <typename T, std::size_t ... Is>
  constexpr std::array<T, sizeof...(Is)>
  create_array(T value, std::index_sequence<Is...>)
  {
    // cast Is to void to remove the warning: unused value
    return {{(static_cast<void>(Is), value)...}};
  }
}

template <std::size_t N, typename T>
constexpr std::array<T, N> create_array(const T& value)
{
  return detail::create_array(value, std::make_index_sequence<N>());
}
Şöyle yaparız. Böylece 10'luk bir diziyi hep 7 değeri ile ilklendiririz.
auto a = create_array<10 /*, int*/>(7); // auto is std::array<int, 10>

Örnek
Eğer index 0 olursa aslında boş bir dizidir. Bu ikisini ayırt etmek için şöyle yaparız.
auto func1(std::index_sequence<>)
{
  return func2();
}

template <std::size_t... I>
auto func1(std::index_sequence<I...>)
{
  auto var = get_tuple();
  return func2(std::get<I>(var)...);
}
Şöyle yaparsak ilk metoda girer.
auto a = func1(std::make_index_sequence<0>());
Index'in Üst Sınırı
Örnek
Şöyle yaparız.
template <std::size_t... I>
auto func1(std::index_sequence<I...>)
{
  if constexpr (sizeof ...(I) == 0) {
    return func2();
  } else {
    auto var = get_tuple();
    return func2(std::get<I>(var)...);
  }
}
Şöyle yaparsak ilk koşula girer.
auto a = func1(std::make_index_sequence<0>());
Her Index'e Erişmek
std::get<Is>(..) şeklinde yaparız
template<typename F, std::size_t... Is, class Tup>
void foo(F && f, std::index_sequence<Is...>, Tup && tup) {
  std::forward<F>(f)( std::get<Is>(std::move(tup))... );
}
Örnek
Şöyle yaparız.
template <std::size_t... Is>
constexpr auto make_array_from_sequence_impl(std::index_sequence<Is...>)
{
    return std::array<std::size_t, sizeof...(Is)>{Is...};
}

template <typename Seq>
constexpr auto make_array_from_sequence(Seq)
{
    return make_array_from_sequence_impl(Seq{});
}
Çağırmak için şöyle yaparız.
int main()
{
  constexpr auto arr = make_array_from_sequence(make_sequence<6>());
  static_assert(arr[0] == 0);
  static_assert(arr[1] == 1);
  static_assert(arr[2] == 2);
  static_assert(arr[3] == 4);
  static_assert(arr[4] == 5);
  static_assert(arr[5] == 7);
}

Object Slicing

Giriş
Object Slicing C++'ta nesneye yönelik programlamada karşımıza çıkar. C++'ta value semantics olduğu için gerçekleşir. Java gibi dillerde nesneler için reference semantics kullanıldığı için yoktur.

1. Metod Çağrısında Parametre
Metod çağrısında kalıtım varsa bile ata sınıf ilklendirilir ve kullanılır.

Örnek
Şöyle yaparız. Aslında bu örnek tam anlamıyla object slicing sayılmamalı çünkü bilinçli yazılmış bir koda benziyor.
void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);
Örnek
Elimizde şöyle bir kod olsun.
struct Foo
{
  virtual int operator()(void) { return 1; } 
};

struct Bar : public Foo
{
  virtual int operator()(void) override { return 2; }
};

int main()
{
  std::shared_ptr<Foo> p = std::make_shared<Bar>();
  std::cout << (*p)() << std::endl; //Çıktı olarak 2 verir

  std::function<int(void)> f;
  f = *p; //Problem burada
  std::cout << f() << std::endl; //Çıktı olarak 1 verir

  return 0;
}
Açıklaması şöyle. std::function<> değişkeni std::shared_ptr pointer aslında Bar olmasına rağmen Foo tipinden tanımlandığı için Foo gibi düşünür ve çalışır.
... given f = *p;, p is of type std::shared_ptr<Foo>, then the type of *p is Foo& (instead of Bar&).
2. Partial Initialization
Nesneye yönelik programlamada ata sınıf türünden olan bir değişkene kalıtılan bir nesnenin atanabilir. Böylece kalıtımdan dolayı gelen bilgilerin bir kısmı kaybedilmiş (sliced away) olur.

Örnek
Elimizde şöyle bir kod olsun.
class A {
   int foo;
};

class B : public A {
   int bar;
};
Şöyle yaparız.
B b;

A a = b;
Bu atamayı yaparsak a değişkeni b değişkeninin bar alanını içermeyecektir. Esasen bu nesneye yönelik kullanımda zaten beklenen bir davranıştır ve bence bir problem teşkil etmemelidir.

3. Esas Problem
Object Slicing'in oluşturduğu esas problem
A a = b;
A2& a2 = b2;
a2 = b;
şeklinde kullanımda ortaya çıkıyor. C++ assignment operator'ünü virtual kullanmadığı için a2 aslında b nesnesi olmasına rağmen, A'nın assignment operatörü çağrılır. Dolayısıyla b nesnesi yarım (partial) olarak ilklendirilir.

4. Engelleme
Object Slicing engellenebilir.

Örnek
Elimizde şöyle bir kod olsun.
struct foo { int a; };
struct bar : foo { int b; };

int main() {
    bar x{1,2};
    foo y = x; // <- I dont want this to compile!
}
Şöyle yaparız
struct foo 
{ 
  int a; 
  foo() = default; // you have to add this because of the template constructor

  template<typename T>
  foo(const T&) = delete; // error trying to copy anything but a foo

  template<typename T>
  foo& operator=(const T&) = delete; // error assigning anything else but a foo
};