2017-11-17 70 views
7

Mam klasę podobną do kontenera, którą chciałbym przenieść tylko wtedy, gdy typ bazowy jest tylko przenoszeniem, ale można go skopiować. Aby dokonać rzeczy proste, załóżmy copyability jest określona przez pojedynczy parametr szablonu bool:Dodaj konstruktora kopii na podstawie parametrów szablonu

template<bool IsCopyable> 
struct Foo 
{ 
    Foo(); 
    Foo(const Foo&); // only include this when IsCopyable is true 
    Foo(Foo&&); 

    Foo& operator=(const Foo&); // only when IsCopyable 
    Foo& operator=(Foo&&); 
}; 

Teraz, nie mogę po prostu SFINAE konstruktor kopia dala, bo to wymaga zarówno na matrycy i matrycy funkcja nie może być kopią ctor. Ponadto, nie mogę po prostu zrobić static_assert() wewnątrz ctor kopii. Wprawdzie złapie to błędne zastosowanie procedury ctor, ale powoduje również, że klasa z natury kopiuje się z zewnątrz (cecha typu std::is_copy_constructible przyniesie wartość true). Nawiasem mówiąc, niefortunnym wymogiem jest to, że musi on zostać skompilowany w VC++ 2012, więc nie mogę użyć fantazyjnego wyrażenia SFINAE, dziedziczącego ctors, domyślnie/usuniętych funkcji lub constexpr if (niemniej jednak, jeśli masz zgrabne rozwiązanie dla C++ 17 Nadal chciałbym to usłyszeć :))

Oczywistą metodą jest użycie specjalizacji szablonów. Wolałbym nie iść tą drogą, ponieważ w rzeczywistości Foo ma całkiem sporo funkcji i nie lubię się powtarzać. Niemniej jednak, wydaje się to moją jedyną opcję, a może zaimplementować dzielenie niektóre kodu przy użyciu klasy bazowej, tak jak poniżej:

// Base functionality 
template<bool IsCopyable> 
struct FooBase 
{ 
    FooBase(); 

    // move ctor and assignment can go here 
    FooBase(FooBase&&);  
    FooBase& operator=(FooBase&&); 

    // some generic conversion ctor and assignment that I happen to need 
    template<class T> FooBase(T&& t); 
    template<class T> FooBase& operator=(T&&); 

    // ... all sorts of functionality and datamembers  
}; 

// Foo<false> 
template<bool IsCopyable> 
struct Foo : FooBase<IsCopyable> 
{ 
    // can't use inheriting ctors in VS 2012, wrap the calls manually: 
    Foo() { } 
    Foo(Foo&& other) : FooBase<IsCopyable>(std::move(other)) { } 
    Foo& operator=(Foo&& other) 
    { 
     FooBase<IsCopyable>::operator=(std::move(other)); 
     return *this; 
    } 

    template<class T> Foo(T&& t) : FooBase<IsCopyable>(std::forward<T>(t)) { } 
    template<class T> Foo& operator=(T&& t) 
    { 
     FooBase<IsCopyable>::operator=(std::forward<T>(t)); 
     return *this; 
    } 
}; 

// Foo<true> 
template<> 
struct Foo<true> : FooBase<true> 
{ 
    // add these 
    Foo(const Foo&); 
    Foo& operator=(const Foo&); 

    // wrapping calls because of VS 2012: 
    Foo() { } 
    Foo(Foo&& other) : FooBase<true>(std::move(other)) { } 
    Foo& operator=(Foo&& other) 
    { 
     FooBase<true>::operator=(std::move(other)); 
     return *this; 
    } 

    template<class T> Foo(T&& t) : FooBase<true>(std::forward<T>(t)) { } 
    template<class T> Foo& operator=(T&& t) 
    { 
     FooBase<true>::operator=(std::forward<T>(t)); 
     return *this; 
    } 
}; 

To wciąż nieco rozwlekły. Na szczęście staje się czystsze, gdy można użyć dziedziczenia cers i domyślnych funkcji. Niemniej jednak miałem nadzieję, że jest prostszy sposób, najlepiej nie używając klasy bazowej.

Odpowiedz

9
struct nonesuch { 
private: 
    ~nonesuch(); 
    nonesuch(const nonesuch&); 
    void operator=(const nonesuch&); 
}; 

template<bool IsCopyable> 
struct Foo { 
    Foo(const typename std::conditional<IsCopyable, Foo, nonesuch>::type& other) { 
     // copy ctor impl here 
    } 
private: 
    Foo(const typename std::conditional<!IsCopyable, Foo, nonesuch>::type&); 
}; 

Podobnie do zadania.

+0

Wow! Prosty i elegancki. Muszę pamiętać tę sztuczkę. – max66

+0

Świetne rozwiązanie z wyjątkiem tego, że idiom "private method without definition" jest nieaktualny w C++ 11. Te metody zostały wyraźnie usunięte. –

+0

@ArneVogel OP mówi, że nie mogą używać usuniętych funkcji. –

0

Nie wiem, czy mogę współpracować z VC++ 2012, ale ... jeśli dobrze pamiętam, istnieje metoda, która moim zdaniem nieco lepiej dziedziczy po FooBase.

Rodzaj dziedziczenia auto.

można zadeklarować strukturę,

template <bool> 
struct Bar; 

i specjalizują wersję true ze wszystkimi konstruktora/operatorzy

template <> 
struct Bar<true> 
{ 
    Bar() 
    { std::cout << "Bar()" << std::endl; }; 

    Bar (Bar &&) 
    { std::cout << "Bar (Bar &&)" << std::endl; } 

    Bar (Bar const &) 
    { std::cout << "Bar (Bar const &)" << std::endl; } 

    Bar & operator= (Bar &&) 
    { std::cout << "operator= (Bar &&)" << std::endl; return *this; } 

    Bar & operator= (Bar const &) 
    { std::cout << "operator= (Bar const &)" << std::endl; return *this; } 
}; 

Teraz można specjalizować wersję false dziedziczenie z Bar<true>, potwierdzając (= default) co Cię chcę i usuwam (= delete) czego nie chcesz; coś

template <> 
struct Bar<false> : public Bar<true> 
{ 
    using Bar<true>::Bar; 

    // confirmed constructor/operators 
    Bar()     = default; 
    Bar (Bar &&)    = default; 
    Bar & operator= (Bar &&) = default; 

    // deleted constructor/operators 
    Bar (Bar const &)    = delete; 
    Bar & operator= (Bar const &) = delete; 
}; 

Poniżej znajduje się pełny przykład roboczych

#include <iostream> 

template <bool> 
struct Bar; 

template <> 
struct Bar<true> 
{ 
    Bar() 
    { std::cout << "Bar()" << std::endl; }; 

    Bar (Bar &&) 
    { std::cout << "Bar (Bar &&)" << std::endl; } 

    Bar (Bar const &) 
    { std::cout << "Bar (Bar const &)" << std::endl; } 

    Bar & operator= (Bar &&) 
    { std::cout << "operator= (Bar &&)" << std::endl; return *this; } 

    Bar & operator= (Bar const &) 
    { std::cout << "operator= (Bar const &)" << std::endl; return *this; } 
}; 

template <> 
struct Bar<false> : public Bar<true> 
{ 
    using Bar<true>::Bar; 

    // confirmed constructor/operators 
    Bar()     = default; 
    Bar (Bar &&)    = default; 
    Bar & operator= (Bar &&) = default; 

    // deleted constructor/operators 
    Bar (Bar const &)    = delete; 
    Bar & operator= (Bar const &) = delete; 
}; 


int main() 
{ 
    Bar<true> bt1; 
    Bar<false> bf1; 

    auto bt2 = bt1; 
    // auto bf2 = bf1; compilation error (copy constructor deleted) 

    auto bt3 = std::move(bt1); 
    auto bf3 = std::move(bf1); 

    Bar<true> bt4; 
    Bar<false> bf4; 

    bt4 = bt3; 
    // bf4 = bf3; compilation error (operator= (Bar const &) deleted) 

    bt3 = std::move(bt4); 
    bf3 = std::move(bf4); 
} 
3

Nie można użyć klasy podstawowej do kopiowania selektywnie wyłączyć? W ten sposób nie trzeba powtarzać którykolwiek z pozostałych funkcjonalności głównej klasy:

template <bool b> 
struct MaybeCopyable {}; 

template <> 
struct MaybeCopyable<false> { 
    MaybeCopyable(const MaybeCopyable&) = delete; 
    MaybeCopyable& operator=(const MaybeCopyable&) = delete; 
}; 

template<bool IsCopyable> 
struct Foo : MaybeCopyable<IsCopyable> { 
    // other functionality 
}; 

Jeśli MaybeCopyable<false> jest jedną z klas bazowych, konstruktor kopia Foo zostaną automatycznie usunięte.