All streams
Search
Write a publication
Pull to refresh
16
0
Send message
Конечно, просто не приводить реализацию. Реализация «не реализовано» приведена только для того, чтобы пример компилировался.
template <class T, class = void>
struct Foo;
А если ввести вот такой хелпер:
template <class T>
using where = typename std::enable_if<!std::is_base_of<not_implemented, T>::value>::type;

Можно будет писать так:
template <class T>
struct Foo<T, where<Bar<T>>> {};

template <class T, class U>
struct Foo2<T, Container<U>, where<Bar2<T, U>>> {};
Конечно.
#include <iostream>
#include <type_traits>

struct not_implemented {
};

template <class T>
struct Bar : not_implemented {
};

template <class T, class = void>
struct Foo {
    static void f()
    {
        std::cout << "not implemented\n";
    }
};

template <class T>
struct Foo<T, typename std::enable_if<!std::is_base_of<not_implemented, Bar<T>>::value>::type> {
    static void f()
    {
        std::cout << "implemented\n";
    }
};

template <class T>
struct Container {
};

template <class T, class U>
struct Bar2 : not_implemented {
};

template <class T, class U, class = void>
struct Foo2 {
    static void f()
    {
        std::cout << "not implemented\n";
    }
};

template <class T, class U>
struct Foo2<T, Container<U>, typename std::enable_if<!std::is_base_of<not_implemented, Bar2<T, U>>::value>::type> {
    static void f()
    {
        std::cout << "implemented\n";
    }
};

struct A {
};

template <>
struct Bar<A> {
};

template <class U>
struct Bar2<A, U> {
};


struct B {
};

template <class T>
struct Bar2<T, float> {
};

int main()
{
    Foo<A>::f();                    // "implemented"
    Foo<B>::f();                    // "not implemented"
    Foo2<A, Container<int>>::f();   // "implemented"
    Foo2<B, Container<int>>::f();   // "not implemented"
    Foo2<B, Container<float>>::f(); // "implemented"
}
«Хорошее решение» демонстрирует код таким, каким он должен быть, если бы он изначально писался на С++, и писался хорошо.

Вы сравниваете «специализацию» с наследованием, и в «хорошем решении» с перегрузкой функций. Дело не в том, как должно быть «хорошо», а в том, что вы сравниваете разные вещи.

Это сработает только для очень простых примеров, для любой сложной реализации типажей у вас не получится провернуть подобный трюк.

Подобные утверждения требуют какого-то подтверждения. Можете привести пример?
«Хорошее решение» демонстрирует перегрузку функций, а в вашем примере то, что в C++ называется специализацией.
Заметил интересную тенденцию, почти все, кто сравнивает реализацию на каком-либо языке с реализацией на C++, приводят либо заведомо усложненную, либо вообще не имеющую ничего общего с реализацией на сравниваемом языке, реализацию на C++. С какой целью это делается остается только гадать. Корректная калька первого примера на C++ должна выглядеть как-то так:
#include <iostream>

template <class T>
struct Display;

struct Foo {
    int x;
};

template <>
struct Display<Foo> {
    static auto& fmt( const Foo& self, std::ostream& os )
    {
        return os << "(x: " << self.x << ")";
    }
};

struct Bar {
    int x;
    int y;
};

template <>
struct Display<Bar> {
    static auto& fmt( const Bar& self, std::ostream& os )
    {
        return os << "(x: " << self.x << ", y: " << self.y << ")";
    }
};

template <class T>
void print_me( const T& obj )
{
    Display<T>::fmt( obj, std::cout ) << std::endl;
};


int main()
{
    auto foo = Foo{7};
    auto bar = Bar{5, 10};
    print_me( foo );
    print_me( bar );
}
Однако, подход с C# намного проще, многие вещи сделаны за Вас и Вам только остается пользоваться и наслаждаться.

Это неправда. Для вашего примера, решенного в лоб:
#include <iostream>

int main()
{
    const auto N = 8;

    float a[] = { 41982.0,  81.5091, 3.14, 42.666, 54776.45, 342.4556, 6756.2344, 4563.789 };
    float b[] = { 85989.111,  156.5091, 3.14, 42.666, 1006.45, 9999.4546, 0.2344, 7893.789 };
    float c[8];

    for(auto i=0;i<N;++i) {
        c[i]=a[i]+b[i];
    }

    std::cout.precision(10);
    for (size_t i = 0; i < N; i++)
        std::cout << c[i] << std::endl;
}

компилятор с вектороной оптимизацией (напр. g++ -O3) сгенерирует следующий код для цикла:
addps  xmm1,xmm3
addps  xmm0,xmm2
Немного хардкора с длинной арифметикой
#include <iostream>
#include <type_traits>

template <char ... Chars>
struct number {};

std::ostream& operator<< (std::ostream& os, number<>){
    return os;
}

template <char C, char ... Chars>
std::ostream& operator<< (std::ostream& os, number<C, Chars...>){
    os << C << number<Chars...>{};
    return os;
}

template <class A, class B>
struct append;

template <char ... As, char ... Bs>
struct append<number<As...>, number<Bs...>> {
    using type = number<As..., Bs...>;
};

template <class Source, class Result = number<>>
struct reverse;

template <char A, char ... As, char ... Bs>
struct reverse<number<A,As...>, number<Bs...>> {
    using type = typename reverse<number<As...>,number<A,Bs...>>::type;
};

template <char ... Bs>
struct reverse<number<>,number<Bs...>> {
    using type = number<Bs...>;
};

template <class T>
struct remove_zeroes {
    using type = T;
};

template <>
struct remove_zeroes<number<'0'>> {
    using type = number<'0'>;
};

template <char ... As>
struct remove_zeroes<number<'0',As...>> {
    using type = typename remove_zeroes<number<As...>>::type;
};

template <char A, char B, char C>
struct basic_sum {
    static constexpr char H = '0' + ((A-'0') + (B-'0') + (C -'0'))/10;
    static constexpr char L = '0' + ((A-'0') + (B-'0') + (C -'0'))%10;
};

template <class A, class B, char C>
struct partial_sum;

template <char A, char ... As, char B, char ... Bs, char C>
struct partial_sum<number<A,As...>, number<B, Bs...>, C> {
    using L = number<basic_sum<A,B,C>::L>;
    using H = typename partial_sum<number<As...>,number<Bs...>,basic_sum<A,B,C>::H>::type;
    using type = typename append<L,H>::type;
};
template <char A, char ... As, char C>
struct partial_sum<number<A,As...>,number<>,C> {
    using L = number<basic_sum<A,'0',C>::L>;
    using H = typename partial_sum<number<As...>,number<>,basic_sum<A,'0',C>::H>::type;
    using type = typename append<L,H>::type;
};

template <char B, char ... Bs, char C>
struct partial_sum<number<>,number<B,Bs...>,C> {
    using L = number<basic_sum<'0',B,C>::L>;
    using H = typename partial_sum<number<>,number<Bs...>,basic_sum<B,'0',C>::H>::type;
    using type = typename append<L,H>::type;
};

template <char C>
struct partial_sum<number<>,number<>,C> {
    using L = number<C>;
    using H = number<>;
    using type = typename append<L,H>::type;
};

template <class A, class B>
using sum = typename remove_zeroes<typename reverse<typename partial_sum<typename reverse<A>::type,typename reverse<B>::type,'0'>::type>::type>::type;

template <class>
struct decrement_helper;

template <char A, char... As>
struct decrement_helper<number<A,As...>> {
    using L = number<(A=='0')?'9':(A-1)>;
    using H = typename std::conditional<A=='0',typename decrement_helper<number<As...>>::type,number<As...>>::type;
    using type = typename append<L,H>::type;
};

template <>
struct decrement_helper<number<>> {
    using type = number<>;
};

template <class T>
using decrement = typename remove_zeroes<typename reverse<typename decrement_helper<typename reverse<T>::type>::type>::type>::type;

template <class K, class N>
struct bcl_helper {
    using type = sum<typename bcl_helper<K,decrement<N>>::type,typename bcl_helper<decrement<K>,decrement<N>>::type>;
};

template <class N>
struct bcl_helper<N,N> {
    using type = number<'1'>;
};

template <class N>
struct bcl_helper<number<'0'>,N> {
    using type = number<'1'>;
};

template <typename CharT, CharT ...String> auto operator "" _n() {
    return number<String...>();
}

template <class K, class N>
auto bcl(K,N) {
    return typename bcl_helper<K,N>::type();
}

int main(int /*argc*/, char** /*argv*/)
{
    std::cout << bcl("50"_n,"100"_n) << std::endl;
}

Нет ничего проще:
#include <type_traits>
#include <limits>

template <class K, class N, bool, bool>
struct bci_helper {
    static constexpr int value = 1;
};

template <class T, T K, T N>
using bci = bci_helper<std::integral_constant<T,K>, std::integral_constant<T,N>, K!=N, K!=0>;

template <class T, T K, T N>
struct bci_helper<std::integral_constant<T,K>, std::integral_constant<T,N>, true, true> {
    static_assert(bci<T, K, N-1>::value <= std::numeric_limits<T>::max() - bci<T, K-1, N-1>::value,"Integer overflow");
    static constexpr T value = bci<T, K, N-1>::value + bci<T,K-1,N-1>::value;
};
Является эквивалентом приведения только для простых типов.
Но соглашусь, статья является не полной без описания явных и неявных приведений типов в конструкторах и вызовах функций, из чего и следует данный вариант приведения.
Да, но нам еще нужно вывести тип функтора в конструкторе, и при этом сохранить ссылки на обычные функции.
template <class F>
function(F&& f)
  : ptr(std::make_unique<detail::concrete_function<typename detail::remove_reference<F>::type,R,Args...>>(std::forward<F>(f)))
{}
Как раз об этом подумал, но вы меня обогнали. Еще нужно поправить и в конструкторе. Но обычный std::remove_reference_t не подойдет, тогда не получится использовать простые функции. Напишем свой:
Код
#include <tuple>
#include <memory>
#include <cassert>

namespace detail {

template <typename T>
struct call_type : call_type<decltype(&T::operator())>
{};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) volatile > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const volatile > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) &> : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const & > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) volatile & > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const volatile & > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) && > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const && > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) volatile && > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const volatile && > : call_type<ResultType(Args...)> {};

template <typename ResultType, typename ... Args>
struct call_type<ResultType(*)(Args...)> : call_type<ResultType(Args...)> {};

template <typename ResultType, typename ... Args>
struct call_type<ResultType(&)(Args...)> : call_type<ResultType(Args...)> {};

template <typename ResultType, typename ... Args>
struct call_type<ResultType(Args...)>
{
    using type = ResultType(Args...);
};

template <typename T>
struct remove_reference {
    using type = T;
};

template <typename ResultType, typename ... Args>
struct remove_reference<ResultType(&)(Args...)> {
    using type = ResultType(&)(Args...);
};

template <typename T>
struct remove_reference <T&> {
    using type = T;
};

template <class R, class ... Args>
struct any_function {
    virtual ~any_function() {};
    virtual R invoke(Args ... args)=0;
    virtual std::unique_ptr<any_function<R,Args...>> clone() const = 0;
};

template <class F, class R, class ... Args>
struct concrete_function : any_function<R, Args...> {
    template <class Q>
    concrete_function(Q&& f)
      : f_(std::forward<Q>(f))
    {};

    R invoke(Args ... args) override {
        return f_(std::forward<Args>(args)...);
    }

    std::unique_ptr<any_function<R,Args...>> clone() const override {
        return std::make_unique<concrete_function<F,R,Args...>>(f_);
    }
private:
    F f_;
};

}

template <class Function>
struct function;

template <class R, class ... Args>
struct function<R(Args...)> {
    using self_type = function<R(Args...)>;
    using result_type = R;
    using argument_types = std::tuple<Args...>;

    function() {};

    function(self_type&& other)
      : ptr(std::move(other.ptr))
    {}

    function(const self_type& other)
      : ptr(other.clone())
    {}

    self_type& operator=(const self_type& other) {
        ptr = other.clone();
        return *this;
    }

    self_type& operator=(self_type&& other) {
        ptr = std::move(other.ptr);
        return *this;
    }

    template <class F>
    function(F&& f)
      : ptr(std::make_unique<detail::concrete_function<typename detail::remove_reference<F>::type,R,Args...>>(std::forward<F>(f)))
    {}

    R operator()(Args ... args) {
        assert(ptr);
        return ptr->invoke(std::forward<Args>(args)...);
    }
private:
    std::unique_ptr<detail::any_function<R,Args...>> clone() const {
        if(ptr) {
            return ptr->clone();
        } else {
            return nullptr;
        }
    }

    std::unique_ptr<detail::any_function<R,Args...>> ptr;
};

template<class F>
decltype(auto) make_function(F&& f) {
    return function<typename detail::call_type<typename detail::remove_reference<F>::type>::type>(std::forward<F>(f));
}

Я это вижу как-то так:
Код
#include <tuple>
#include <memory>
#include <cassert>

namespace detail {

template <typename T>
struct call_type : call_type<decltype(&T::operator())>
{};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) volatile > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const volatile > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) &> : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const & > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) volatile & > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const volatile & > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) && > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const && > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) volatile && > : call_type<ResultType(Args...)> {};

template <typename ClassType, typename ResultType, typename ... Args>
struct call_type<ResultType(ClassType::*)(Args...) const volatile && > : call_type<ResultType(Args...)> {};

template <typename ResultType, typename ... Args>
struct call_type<ResultType(*)(Args...)> : call_type<ResultType(Args...)> {};

template <typename ResultType, typename ... Args>
struct call_type<ResultType(&)(Args...)> : call_type<ResultType(Args...)> {};

template <typename ResultType, typename ... Args>
struct call_type<ResultType(Args...)>
{
    using type = ResultType(Args...);
};

template <class R, class ... Args>
struct any_function {
    virtual ~any_function() {};
    virtual R invoke(Args ... args)=0;
    virtual std::unique_ptr<any_function<R,Args...>> clone() const = 0;
};

template <class F, class R, class ... Args>
struct concrete_function : any_function<R, Args...> {
    template <class Q>
    concrete_function(Q&& f)
      : f_(std::forward<Q>(f))
    {};

    R invoke(Args ... args) override {
        return f_(std::forward<Args>(args)...);
    }

    std::unique_ptr<any_function<R,Args...>> clone() const override {
        return std::make_unique<concrete_function<F,R,Args...>>(f_);
    }
private:
    F f_;
};

}

template <class Function>
struct function;

template <class R, class ... Args>
struct function<R(Args...)> {
    using self_type = function<R(Args...)>;
    using result_type = R;
    using argument_types = std::tuple<Args...>;

    function() {};

    function(self_type&& other)
      : ptr(std::move(other.ptr))
    {}

    function(const self_type& other)
      : ptr(other.clone())
    {}

    self_type& operator=(const self_type& other) {
        ptr = other.clone();
        return *this;
    }

    self_type& operator=(self_type&& other) {
        ptr = std::move(other.ptr);
        return *this;
    }

    template <class F>
    function(F&& f)
      : ptr(std::make_unique<detail::concrete_function<F,R,Args...>>(std::forward<F>(f)))
    {}

    R operator()(Args ... args) {
        assert(ptr);
        return ptr->invoke(std::forward<Args>(args)...);
    }
private:
    std::unique_ptr<detail::any_function<R,Args...>> clone() const {
        if(ptr) {
            return ptr->clone();
        } else {
            return nullptr;
        }
    }

    std::unique_ptr<detail::any_function<R,Args...>> ptr;
};

template<class F>
decltype(auto) make_function(F&& f) {
    return function<typename detail::call_type<F>::type>(std::forward<F>(f));
}

Под «перспективными» подразумеваются проекты, ориентированные на данную архитектуру, чтобы начать разработку уже сейчас.
Спецификатор register уже давно как deprecated. И в целом в рассматриваемом примере нет ничего, что касалось бы оптимизации. Выравнивание может незначительно повлиять на std::sort, а для рассматриваемого алгоритма оно не имеет значения. Список инициализации в принципе не является оптимизацией (наоборот, не использование списков инициализации можно считать дурным тоном), к тому же никаким образом не влияет на рассматриваемый алгоритм.
Как это выглядит для индивидуального пользователя:
Покупка PVS-Studio лично для себя практически нереальна. И даже CppCat все равно слишком дорог.
Как это выглядит для корпоративного пользователя:
Зачем покупать CppCat — заведомо ущербный аналог PVS-Studio. Даже если на самом деле это не так, разница в цене создает реальное психологическое давление.
В вашем примере происходит лишнее выделение памяти под хендл.

Именно поэтому unique_ptr и не является хорошим инструментом для реализации scope_guard.
При освобождении ресурса функция CloseHandle может бросить исключение и память, выделенная под хендл, не будет освобождена.


Тут у нас есть несколько решений — потребовать условия noexcept(CloseHandle(*h)), просто проигнорировать исключение посредством try-catch, или передать это исключение для дальнейшей обработки:
struct HandleDeleter {
  void operator()(Handle* h) {
  	std::exception_ptr ex;
  	try {
  		CloseHandle(*h); 
  	} catch (...) {
  		ex = std::current_exception();
  	}
	delete h;
  	if (ex){
  		std::rethrow_exception(ex);
  	}
  }
};

Что, впрочем, не является лучшей идеей, учитывая, что deleter будет вызван в деструкторе unique_ptr.
Все так. Спекуляция в том, что проблемы с использованием std::unique_ptr не по назначению выставляются как его недостатки. Очевидно, что unique_ptr не является панацеей, а unique_resource востребован.
Одно дело что вы используете «трюк» (по сути «хак»), а совсем другое, что он преподносится как единственно верный вариант.
#include <memory>

using Handle = int;
Handle CreateHandle() { Handle h{ -1 }; /*...*/ return h; }
void CloseHandle(Handle& h) { /* ... */ }

struct HandleDeleter {
  void operator()(Handle* h) { CloseHandle(*h);  delete h; }
};
using ScopedHandle = std::unique_ptr<Handle, HandleDeleter>;

int main() {
  ScopedHandle h{ new Handle(CreateHandle()) };
}

Information

Rating
Does not participate
Location
Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Software Architect
PostgreSQL
C#
C++
Linux
Docker
Kubernetes
High-loaded systems
Designing application architecture
Database design