2015-06-17 3 views
9

изобретая дискриминационный союз/меченый вариант я делаю вывод, что существует особая необходимость в такой функции, как «сделать деструктор тривиально на определенных условиях во время компиляции». Я имею в виду какой-то SFINAE или что-то вроде (псевдокод):тривиальный деструктор Условно

template< typename ...types > 
struct X 
{ 
    ~X() = default((std::is_trivially_destructible<types>{} && ...)) 
    { 
     // non-trivial code here 
    } 
}; 

Это означает, что если условие default(*) является true, то определение деструктора равно ~X() = default;, но если это false то { // ... } тело вместо.

#pragma once 
#include <type_traits> 
#include <utility> 
#include <experimental/optional> 

#include <cassert> 

template< typename ...types > 
class U; 

template<> 
class U<> 
{ 

    U() = delete; 

    U(U &) = delete; 
    U(U const &) = delete; 
    U(U &&) = delete; 
    U(U const &&) = delete; 

    void operator = (U &) = delete; 
    void operator = (U const &) = delete; 
    void operator = (U &&) = delete; 
    void operator = (U const &&) = delete; 

}; 

template< typename first, typename ...rest > 
class U< first, rest... > 
{ 

    struct head 
    { 

     std::size_t which_; 
     first value_; 

     template< typename ...types > 
     constexpr 
     head(std::experimental::in_place_t, types &&... _values) 
      : which_{sizeof...(rest)} 
      , value_(std::forward<types>(_values)...) 
     { ; } 

     template< typename type > 
     constexpr 
     head(type && _value) 
      : head(std::experimental::in_place, std::forward<type>(_value)) 
     { ; } 

    }; 

    using tail = U<rest...>; 

    union 
    { 

     head head_; 
     tail tail_; 

    }; 

    template< typename ...types > 
    constexpr 
    U(std::true_type, types &&... _values) 
     : head_(std::forward<types>(_values)...) 
    { ; } 

    template< typename ...types > 
    constexpr 
    U(std::false_type, types &&... _values) 
     : tail_(std::forward<types>(_values)...) 
    { ; } 

public : 

    using this_type = first; // place for recursive_wrapper filtering 

    constexpr 
    std::size_t 
    which() const 
    { 
     return head_.which_; 
    } 

    constexpr 
    U() 
     : U(typename std::is_default_constructible<this_type>::type{}, std::experimental::in_place) 
    { ; } 

    U(U &) = delete; 
    U(U const &) = delete; 
    U(U &&) = delete; 
    U(U const &&) = delete; 

    template< typename type > 
    constexpr 
    U(type && _value) 
     : U(typename std::is_same< this_type, std::decay_t<type> >::type{}, std::forward<type>(_value)) 
    { ; } 

    template< typename ...types > 
    constexpr 
    U(std::experimental::in_place_t, types &&... _values) 
     : U(typename std::is_constructible< this_type, types... >::type{}, std::experimental::in_place, std::forward<types>(_values)...) 
    { ; } 

    void operator = (U &) = delete; 
    void operator = (U const &) = delete; 
    void operator = (U &&) = delete; 
    void operator = (U const &&) = delete; 

    template< typename type > 
    constexpr 
    void 
    operator = (type && _value) & 
    { 
     operator std::decay_t<type> &() = std::forward<type>(_value); 
    } 

    constexpr 
    explicit 
    operator this_type &() & 
    { 
     assert(sizeof...(rest) == which()); 
     return head_.value_; 
    } 

    constexpr 
    explicit 
    operator this_type const &() const & 
    { 
     assert(sizeof...(rest) == which()); 
     return head_.value_; 
    } 

    constexpr 
    explicit 
    operator this_type &&() && 
    { 
     assert(sizeof...(rest) == which()); 
     return std::move(head_.value_); 
    } 

    constexpr 
    explicit 
    operator this_type const &&() const && 
    { 
     assert(sizeof...(rest) == which()); 
     return std::move(head_.value_); 
    } 

    template< typename type > 
    constexpr 
    explicit 
    operator type &() & 
    { 
     return static_cast< type & >(tail_); 
    } 

    template< typename type > 
    constexpr 
    explicit 
    operator type const &() const & 
    { 
     return static_cast< type const & >(tail_); 
    } 

    template< typename type > 
    constexpr 
    explicit 
    operator type &&() && 
    { 
     //return static_cast< type && >(std::move(tail_)); // There is known clang++ bug #19917 for static_cast to rvalue reference. 
     return static_cast< type && >(static_cast< type & >(tail_)); // workaround 
    } 

    template< typename type > 
    constexpr 
    explicit 
    operator type const &&() const && 
    { 
     //return static_cast< type const && >(std::move(tail_)); 
     return static_cast< type const && >(static_cast< type const & >(tail_)); 
    } 

    ~U() 
    { 
     if (which() == sizeof...(rest)) { 
      head_.~head(); 
     } else { 
      tail_.~tail(); 
     } 
    } 

}; 

// main.cpp 
#include <cstdlib> 

int 
main() 
{ 
    U< int, double > u{1.0}; 
    assert(static_cast<double>(u) == 1.0); 
    u = 0.0; 
    assert(static_cast<double>(u) == 0.0); 
    U< int, double > w{1}; 
    assert(static_cast<int>(w) == 1); 
    return EXIT_SUCCESS; 
} 

В этом примере для создания класса U буквальным типа (в случае first, rest... является все тривиальной разрушаемостью) можно определить почти так же, как U класса (V), но без определения деструктора ~U (т. Е. Является литеральным типом, если все нисходящие типы являются литералами). Затем определите тип шаблона псевдоним

template< typename ...types > 
using W = std::conditional_t< (std::is_trivially_destructible<types>{} && ...), V<types...>, U<types...> >; 

и переопределить using tail = W<rest...>; в обоих U и V. Следовательно, существует два почти одинаковых класса, отличается только наличием деструктора. Вышеуказанный подход требует чрезмерного дублирования кода.

Проблема также связана с тривиально копирующими/переносными присваиваемыми типами и operator =, а также всеми другими условиями для типа std::is_trivially_copyable. 5 условий дают полностью 2^5 комбинации для реализации.

Есть ли готовый к использованию техники (и менее многословной, то описанной выше одного) выражающейся настоящий C++ я скучаю, или, возможно, в ближайшее время предложения ?

Другим мыслимым подходом является (особенность языка), чтобы отметить деструктор как constexpr и предоставить компилятору проверить, является ли тело эквивалентным тривиальному во время создания экземпляра или нет.

UPDATE:

код упрощен как указано в комментариях: union стал union -как класса. Удалены noexcept спецификаторы.

+1

Я не знаю сценарий, который может привести к такой конструкции деструктора. Я также * чувствую *, что бы ни был этот сценарий, который можно было бы решить при правильном использовании RAII (реализованными аргументами шаблона 'types ...'). – Nawaz

+1

Я не понимаю, почему вы хотите/нуждаетесь в 'X' для хранения значений и управления уничтожением напрямую. Возможно, есть элемент или базовый шаблон для значения 'std :: is_trivially_destructible', тогда вы можете специализировать деструктор' Member_Or_Base 'для предикации желаемого уничтожения. И это слишком много кода для публикации - когда у вас есть конкретная проблема, создайте минимальный код, иллюстрирующий это. –

+0

@Nawaz Сценарий возникает, когда нам нужно определить * условно-литерал * класс.Я предоставляю полный пример и одно возможное ошибочное решение. – Orient

ответ

1

Условный деструктор может быть реализован через дополнительный промежуточный слой с помощью специализированной специализации. Например:

Live Demo on Coliru

#include <type_traits> 
#include <iostream> 
#include <vector> 

using namespace std; 

template<typename T> 
class storage 
{ 
    aligned_storage_t<sizeof(T)> buf; 

    storage(storage&&) = delete; 
public: 
    storage() 
    { 
     new (&buf) T{}; 
    } 
    T &operator*() 
    { 
     return *static_cast<T*>(&buf); 
    } 
    void destroy() 
    { 
     (**this).~T(); 
    } 
}; 

template<typename T, bool destructor> 
struct conditional_storage_destructor 
{ 
    storage<T> x; 
}; 

template<typename T> 
struct conditional_storage_destructor<T, true> : protected storage<T> 
{ 
    storage<T> x; 

    ~conditional_storage_destructor() 
    { 
     x.destroy(); 
    } 
}; 

template<typename T> 
class wrapper 
{ 
    conditional_storage_destructor<T, not is_trivially_destructible<T>::value> x; 
public: 
    T &operator*() 
    { 
     return *(x.x); 
    } 
}; 

int main() 
{ 
    static_assert(is_trivially_destructible< wrapper<int> >::value); 
    static_assert(not is_trivially_destructible< wrapper<vector<int>> >::value); 

    cout << "executed" << endl; 
} 
+0

Я знаю это. Но это похоже на нечто чуждое и избыточное. В любом случае ваш пример вместе с * union-like classes * может полностью решить проблему. Увы, очень аккуратно. – Orient

+0

@Orient Класс 'static if' на уровне класса может улучшить синтаксис. Андрей Александреску обсуждает аналогичную ситуацию в своем выступлении [«Static If I Had Hammer»] (https://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Static-If-I-Had-a-Hammer). Существуют некоторые предложения 'static if' для ISO C++ - например [N3329] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3329.pdf). –

+0

Интересная вещь. – Orient

Смежные вопросы