簡體   English   中英

為什么這個包裝分配器的構造函數在模板替換期間采用錯誤的類型(完美的轉發 ctor)?

[英]Why does this wrapping allocator's constructor take in the wrong type during template substitution (perfect forwarding ctor)?

對於某些上下文,我正在編寫一個分配器,它將基本分配器作為模板類型,除了將allocate()deallocate()調用轉發到底層分配器成員之外什么都不做。 使用此自定義分配器創建std::vector可以正常工作。 我嘗試編寫一個make_shared包裝器,默認情況下將使用dummy_allocator<T, std::allocator<T>>但它沒有成功。 這是一個可重現的示例:

#include <memory>
namespace test {

template<typename T, typename base_allocator=std::allocator<T>>
class dummy_allocator {
public:
    typedef typename std::allocator_traits<base_allocator>::size_type size_type;
    typedef typename std::allocator_traits<base_allocator>::difference_type difference_type;
    typedef typename std::allocator_traits<base_allocator>::pointer pointer;
    typedef typename std::allocator_traits<base_allocator>::const_pointer const_pointer;
    typedef typename std::allocator_traits<base_allocator>::value_type value_type;

    template<class U>
    struct rebind {
        typedef dummy_allocator<U,
                typename std::allocator_traits<base_allocator>::template rebind_alloc<U>> other;
    };

    template<typename... Args>
    dummy_allocator(Args &&... args) noexcept : alloc(std::forward<Args>(args)...) {}


    dummy_allocator(const dummy_allocator& a) = default;

    [[nodiscard]] T *allocate(std::size_t n) {
        T *p = alloc.allocate(n);
        return p;
    }

    void deallocate(T *p, std::size_t size) noexcept {
        alloc.deallocate(p, size);
    }

private:
    base_allocator alloc;
};

/// Allocate using a wrapped version of passed in allocator
template <typename T, typename Alloc, typename... Args>
std::shared_ptr<T> allocate_shared(const Alloc& alloc, Args&&... args) {
    auto dummy_alloc = dummy_allocator<T, Alloc>(alloc);
    return std::allocate_shared<T>(dummy_alloc, std::forward<Args>(args)...);
}

/// Create a shared pointer from a default stl allocator wrapped in profile allocator.
template <typename T, typename... Args>
std::shared_ptr<T> make_shared(Args&&... args) {
    return test::allocate_shared<T>(std::allocator<T>(), std::forward<Args>(args)...);
}

} // namespace test

int main() {
    auto ptr = test::make_shared<double>();
    return 0;
}

當我運行上面的代碼時,編譯器產生了一些奇怪的模板替換失敗錯誤:

/usr/include/c++/10.1.0/bits/shared_ptr_base.h:679:43:   required from ‘std::__shared_count<_Lp>::__shared_count(_Tp*&, std::_Sp_alloc_shared_tag<_Alloc>, _Args&& ...) [with _Tp = double; _Alloc = test::dummy_allocator<double, std::allocator<double> >; _Args = {}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]’
/usr/include/c++/10.1.0/bits/shared_ptr_base.h:1371:71:   required from ‘std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = test::dummy_allocator<double, std::allocator<double> >; _Args = {}; _Tp = double; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]’
/usr/include/c++/10.1.0/bits/shared_ptr.h:408:59:   required from ‘std::shared_ptr<_Tp>::shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = test::dummy_allocator<double, std::allocator<double> >; _Args = {}; _Tp = double]’
/usr/include/c++/10.1.0/bits/shared_ptr.h:859:14:   required from ‘std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = double; _Alloc = test::dummy_allocator<double, std::allocator<double> >; _Args = {}]’
/home/ray/home/testing/src/alloc.cpp:44:35:   required from ‘std::shared_ptr<_Tp> test::allocate_shared(const Alloc&, Args&& ...) [with T = double; Alloc = std::allocator<double>; Args = {}]’
/home/ray/home/testing/src/alloc.cpp:50:36:   required from ‘std::shared_ptr<_Tp> test::make_shared(Args&& ...) [with T = double; Args = {}]’
/home/ray/home/testing/src/alloc.cpp:86:46:   required from here
/home/ray/home/testing/src/alloc.cpp:25:82: error: no matching function for call to ‘std::allocator<std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic> >::allocator(const test::dummy_allocator<double, std::allocator<double> >&)’
   25 |     dummy_allocator(Args &&... args) noexcept : alloc(std::forward<Args>(args)...) {}
      |                                                                                  ^
In file included from /usr/include/c++/10.1.0/list:61,
                 from /home/ray/home/testing/src/alloc.cpp:3:
/usr/include/c++/10.1.0/bits/allocator.h:157:2: note: candidate: ‘template<class _Tp1> constexpr std::allocator< <template-parameter-1-1> >::allocator(const std::allocator<_Tp1>&) [with _Tp1 = _Tp1; _Tp = std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic>]’
  157 |  allocator(const allocator<_Tp1>&) _GLIBCXX_NOTHROW { }
      |  ^~~~~~~~~
/usr/include/c++/10.1.0/bits/allocator.h:157:2: note:   template argument deduction/substitution failed:
/home/ray/home/testing/src/alloc.cpp:25:82: note:   ‘const test::dummy_allocator<double, std::allocator<double> >’ is not derived from ‘const std::allocator<_Up>’
   25 |     dummy_allocator(Args &&... args) noexcept : alloc(std::forward<Args>(args)...) {}
      |                                                                                  ^
In file included from /usr/include/c++/10.1.0/list:61,
                 from /home/ray/home/testing/src/alloc.cpp:3:
/usr/include/c++/10.1.0/bits/allocator.h:147:7: note: candidate: ‘constexpr std::allocator< <template-parameter-1-1> >::allocator(const std::allocator< <template-parameter-1-1> >&) [with _Tp = std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic>]’
  147 |       allocator(const allocator& __a) _GLIBCXX_NOTHROW
      |       ^~~~~~~~~
/usr/include/c++/10.1.0/bits/allocator.h:147:34: note:   no known conversion for argument 1 from ‘const test::dummy_allocator<double, std::allocator<double> >’ to ‘const std::allocator<std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic> >&’
  147 |       allocator(const allocator& __a) _GLIBCXX_NOTHROW
      |                 ~~~~~~~~~~~~~~~~~^~~
/usr/include/c++/10.1.0/bits/allocator.h:144:7: note: candidate: ‘constexpr std::allocator< <template-parameter-1-1> >::allocator() [with _Tp = std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic>]’
  144 |       allocator() _GLIBCXX_NOTHROW { }
      |       ^~~~~~~~~
/usr/include/c++/10.1.0/bits/allocator.h:144:7: note:   candidate expects 0 arguments, 1 provided
... (The above error basically is repeated 2 more times)

特別是,這個錯誤似乎代表了正在發生的問題:

error: no matching function for call to ‘std::allocator<std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic> >::allocator(const test::dummy_allocator<double, std::allocator<double> >&)’

它基本上說“在dummy_allocator的構造函數中,您不能將dummy_allocator傳遞給std::allocator的構造函數”。 但我不這樣做。 allocate_shared中,我將std::allocator傳遞給dummy_allocator

我真的在閱讀編譯器錯誤時撓了撓頭,但對我做錯了什么沒有得出任何結論。 任何幫助將不勝感激!

編輯:我想我有一種預感,無論 shared_ptr 在下面做什么魔術,它都在嘗試做我的 dummy_allocator 的復制構造函數,而完美的轉發是捕獲復制構造而不是實際的復制構造函數。 但是,我知道如何解決這個問題,因為它是一個可變參數模板,並且我不能使用std::is_same<Args, dummy_allocator>作為完美轉發構造函數中的 requires 子句。

因此,正如 Daniel Langr 所指出的,僅復制構造 dummy_allocator 將失敗。 我用以下 requires 子句解決了這個問題:

...
    template <typename T1, typename ...TV>
    struct is_dummy : std::is_same<typename std::decay<T1>::type, dummy_allocator<T, base_allocator>>{
    };

    template<typename... Args>
    requires (!is_dummy<Args...>::value)
    dummy_allocator(Args &&... args) noexcept : alloc(std::forward<Args>(args)...) {}

    dummy_allocator() = default;
    dummy_allocator(const dummy_allocator& a) = default;
...

但是,這還不足以解決shared_ptr問題,其中錯誤有點長... *

所以這是你得到的錯誤:

#include <memory>
namespace test {

template<typename T, typename base_allocator=std::allocator<T>>
class dummy_allocator {
public:
    /// Necessary for allocators, propagate exactly what the base_allocator
    /// wants.
    typedef typename std::allocator_traits<base_allocator>::size_type size_type;
    typedef typename std::allocator_traits<base_allocator>::difference_type difference_type;
    typedef typename std::allocator_traits<base_allocator>::pointer pointer;
    typedef typename std::allocator_traits<base_allocator>::const_pointer const_pointer;
    typedef typename std::allocator_traits<base_allocator>::value_type value_type;

    template<class U>
    struct rebind {
        typedef dummy_allocator<U,
                typename std::allocator_traits<base_allocator>::template rebind_alloc<U>> other;
    };

    template <typename T1, typename ...TV>
    struct is_dummy : std::is_same<typename std::decay<T1>::type, dummy_allocator<T, base_allocator>>{
    };

    template<typename... Args>
    requires (!is_dummy<Args...>::value)
    dummy_allocator(Args &&... args) noexcept : alloc(std::forward<Args>(args)...) {}

    dummy_allocator() = default;
    dummy_allocator(const dummy_allocator& a) = default;

    [[nodiscard]] T *allocate(std::size_t n) {
        T *p = alloc.allocate(n);
        return p;
    }

    void deallocate(T *p, std::size_t size) noexcept {
        alloc.deallocate(p, size);
    }

private:
    base_allocator alloc;
};

/// Allocate using a wrapped version of passed in allocator
template <typename T, typename Alloc, typename... Args>
auto allocate_shared(const Alloc& alloc, Args&&... args) {
    auto dummy_alloc = dummy_allocator<T, Alloc>(alloc);
    return std::allocate_shared<T>(dummy_alloc, std::forward<Args>(args)...);
}

/// Create a shared pointer from a default stl allocator wrapped in profile allocator.
template <typename T, typename... Args>
auto make_shared(Args&&... args) {
    return test::allocate_shared<T>(std::allocator<T>(), std::forward<Args>(args)...);
}

} // namespace test

int main() {
    // This will fail
    auto ptr = test::make_shared<double>();

    // This will now work
    auto dummy_alloc = test::dummy_allocator<int, std::allocator<int>>();
    auto dummy_alloc2 = test::dummy_allocator<int, std::allocator<int>>(dummy_alloc);
    return 0;
}

相關錯誤:

/usr/include/c++/10.1.0/bits/shared_ptr_base.h:679:43:   required from ‘std::__shared_count<_Lp>::__shared_count(_Tp*&, std::_Sp_alloc_shared_tag<_Alloc>, _Args&& ...) [with _Tp = double; _Alloc = test::dummy_allocator<double, std::allocator<double> >; _Args = {}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]’
/usr/include/c++/10.1.0/bits/shared_ptr_base.h:1371:71:   required from ‘std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = test::dummy_allocator<double, std::allocator<double> >; _Args = {}; _Tp = double; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]’
/usr/include/c++/10.1.0/bits/shared_ptr.h:408:59:   required from ‘std::shared_ptr<_Tp>::shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = test::dummy_allocator<double, std::allocator<double> >; _Args = {}; _Tp = double]’
/usr/include/c++/10.1.0/bits/shared_ptr.h:859:14:   required from ‘std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = double; _Alloc = test::dummy_allocator<double, std::allocator<double> >; _Args = {}]’
/home/ray/home/test/src/alloc.cpp:53:35:   required from ‘auto test::allocate_shared(const Alloc&, Args&& ...) [with T = double; Alloc = std::allocator<double>; Args = {}]’
/home/ray/home/test/src/alloc.cpp:59:36:   required from ‘auto test::make_shared(Args&& ...) [with T = double; Args = {}]’
/home/ray/home/test/src/alloc.cpp:95:46:   required from here
/home/ray/home/test/src/alloc.cpp:31:82: error: no matching function for call to ‘std::allocator<std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic> >::allocator(const test::dummy_allocator<double, std::allocator<double> >&)’
   31 |     dummy_allocator(Args &&... args) noexcept : alloc(std::forward<Args>(args)...) {}
      |                                                                                  ^
In file included from /usr/include/c++/10.1.0/list:61,
                 from /home/ray/home/test/src/alloc.cpp:3:
/usr/include/c++/10.1.0/bits/allocator.h:157:2: note: candidate: ‘template<class _Tp1> constexpr std::allocator< <template-parameter-1-1> >::allocator(const std::allocator<_Tp1>&) [with _Tp1 = _Tp1; _Tp = std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic>]’
  157 |  allocator(const allocator<_Tp1>&) _GLIBCXX_NOTHROW { }
      |  ^~~~~~~~~
/usr/include/c++/10.1.0/bits/allocator.h:157:2: note:   template argument deduction/substitution failed:
/home/ray/home/test/src/alloc.cpp:31:82: note:   ‘const test::dummy_allocator<double, std::allocator<double> >’ is not derived from ‘const std::allocator<_Up>’
   31 |     dummy_allocator(Args &&... args) noexcept : alloc(std::forward<Args>(args)...) {}
      |                                                                                  ^
In file included from /usr/include/c++/10.1.0/list:61,
                 from /home/ray/home/test/src/alloc.cpp:3:
/usr/include/c++/10.1.0/bits/allocator.h:147:7: note: candidate: ‘constexpr std::allocator< <template-parameter-1-1> >::allocator(const std::allocator< <template-parameter-1-1> >&) [with _Tp = std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic>]’
  147 |       allocator(const allocator& __a) _GLIBCXX_NOTHROW
      |       ^~~~~~~~~
/usr/include/c++/10.1.0/bits/allocator.h:147:34: note:   no known conversion for argument 1 from ‘const test::dummy_allocator<double, std::allocator<double> >’ to ‘const std::allocator<std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic> >&’
  147 |       allocator(const allocator& __a) _GLIBCXX_NOTHROW
      |                 ~~~~~~~~~~~~~~~~~^~~
/usr/include/c++/10.1.0/bits/allocator.h:144:7: note: candidate: ‘constexpr std::allocator< <template-parameter-1-1> >::allocator() [with _Tp = std::_Sp_counted_ptr_inplace<double, test::dummy_allocator<double, std::allocator<double> >, __gnu_cxx::_S_atomic>]’
  144 |       allocator() _GLIBCXX_NOTHROW { }
      |       ^~~~~~~~~
/usr/include/c++/10.1.0/bits/allocator.h:144:7: note:   candidate expects 0 arguments, 1 provided

只需刪除可變參數構造函數並添加這兩個:

dummy_allocator(const base_allocator& a) : alloc(a)
{}

template<class U, class Alloc>
dummy_allocator(const dummy_allocator<U, Alloc>& a) : alloc(a.alloc)
{}

演示


如果我想將 arguments 轉發到需要 arguments 的分配器,這並不能解決問題:(

然后你可以使用std::is_constructible

template<class... Args>
dummy_allocator(Args&&... args)
requires(std::is_constructible_v<base_allocator, Args...>) 
    : alloc(std::forward<Args>(args)...)
{}

template<class U, class Alloc>
dummy_allocator(const dummy_allocator<U, Alloc>& a) : alloc(a.alloc)
{}

演示


請注意,在這兩種情況下都需要template<class U, class Alloc> dummy_allocator構造函數,因為dummy_allocator a(b) b任何重新綁定的分配器類型的 b 都應該是格式良好的

我覺得你的預感是對的。 人們總是必須小心完美的轉發構造函數和正常的復制構造函數。 我認為有多種解決方案,但我還沒有測試過其中任何一個。

首先,您可以通過將dummy_allocator(dummy_allocator&)轉發添加到普通復制構造函數來提供比完美轉發構造函數更好的匹配。 當傳遞的dummy_allocator不是 const 並且因此復制構造函數必須進行 const 轉換時,完美的轉發構造函數比復制構造函數匹配得更好。 但這還需要重載移動構造函數(我認為),否則當有人試圖移動dummy_allocator時,您會遇到相同的錯誤。

更棘手但可能更安全的是執行以下操作:

template<class... Args>
dummy_allocator(Args&&... args) requires (sizeof...(Args) != 1) : alloc(std::forward<Args>(args)...) {}

template<class Arg>
dummy_allocator(Arg&& arg) requires (!std::is_same_v<std::decay_t<Arg>, dummy_allocator>) : alloc(std::forward<Arg>(arg)) {}

dummy_allocator(const dummy_allocator&) = default;

我希望我的要求條款是正確的,我還沒有做太多的工作。 但是在構造函數中做 SFINAE 是一件非常痛苦的事情。

@Evg 的答案在使 arguments 可構造的實現中是正確的,但它還沒有解決我為什么遇到 shared_ptr 問題的問題。 我使用他的實現解決了,因為我覺得使用is_constructible::value是處理它的最佳方法,但我們需要添加一個允許不同類型重新綁定的構造函數:

template<typename T, typename base_allocator=std::allocator<T>>
class dummy_allocator {
public:
    /// Necessary for allocators, propagate exactly what the base_allocator
    /// wants.
    typedef typename std::allocator_traits<base_allocator>::size_type size_type;
    typedef typename std::allocator_traits<base_allocator>::difference_type difference_type;
    typedef typename std::allocator_traits<base_allocator>::pointer pointer;
    typedef typename std::allocator_traits<base_allocator>::const_pointer const_pointer;
    typedef typename std::allocator_traits<base_allocator>::value_type value_type;

    template<class U>
    struct rebind {
        typedef dummy_allocator<U,
                typename std::allocator_traits<base_allocator>::template rebind_alloc<U>> other;
    };

    template <typename T1, typename ...TV>
    struct is_dummy : std::is_same<typename std::decay<T1>::type, dummy_allocator<T, base_allocator>>{
    };

    template<typename... Args>

    dummy_allocator(Args &&... args) requires (std::is_constructible_v<base_allocator, Args...>) : alloc(std::forward<Args>(args)...) {}

    template <typename U, typename A> friend class dummy_allocator;

    // Construct a dummy allocator from another dummy allocator with the same base_allocator but with different type.
    template <typename U>
    dummy_allocator(
            const dummy_allocator<U,
                    typename std::allocator_traits<base_allocator>::template rebind_alloc<U>>& other) noexcept :
            alloc(other.alloc) {}

    dummy_allocator() = default;
    dummy_allocator(const dummy_allocator& a) : alloc(a.alloc) {}

    [[nodiscard]] T *allocate(std::size_t n) {
        T *p = alloc.allocate(n);
        return p;
    }

    void deallocate(T *p, std::size_t size) noexcept {
        alloc.deallocate(p, size);
    }

private:
    base_allocator alloc;
};

特別是,這里的這些行:

    template <typename U, typename A> friend class dummy_allocator;

    // Construct a dummy allocator from another dummy allocator with the same base_allocator but with different type.
    template <typename U>
    dummy_allocator(
            const dummy_allocator<U,
                    typename std::allocator_traits<base_allocator>::template rebind_alloc<U>>& other) noexcept :
            alloc(other.alloc) {}

因為我們需要在分配器的不同底層類型之間有構造函數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM