Attaching all source files minimal example for easy download and compilation. Tested with gcc and msvc. I tried implementing your clone function along with a type traits to find if it exists or not. Enable with define ENABLE_CLONE.
I also enabled forward declaration of foo's constructor (define FORWARD_CONSTRUCTOR). If compiled using this in msvc, the type T becomes known in the constructor (i.e. not incomplete). Gcc disagrees, though.
Output (msvc):
Pimpl::Pimpl: T is incomplete: false
Pimpl::Pimpl(const Pimpl& that): T is incomplete: true
Pimpl::Pimpl(const Pimpl& that): T is cloneable: true
Clone(const detail::Impl*): Is copyable: true
~Pimpl::~Pimpl: T is incomplete: true
~Pimpl::~Pimpl: T is incomplete: true
Output (gcc):
Pimpl::Pimpl: T is incomplete: true
Pimpl::Pimpl(const Pimpl& that): T is incomplete: true
Pimpl::Pimpl(const Pimpl& that): T is cloneable: true
Clone(const detail::Impl*): Is copyable: true
~Pimpl::~Pimpl: T is incomplete: true
~Pimpl::~Pimpl: T is incomplete: true
Here are the source files, for a quick glance:
foo.cpp
Code:
#include "foo.hpp"
struct detail::Impl {};
#ifdef FORWARD_CONSTRUCTOR
foo::foo() {}
#endif
#ifdef ENABLE_CLONE
detail::Impl* Clone(const detail::Impl* that)
{
using T = detail::Impl;
std::cout << "Clone(const detail::Impl*): Is copyable: " << std::boolalpha << stf::detail::IsCopyable<T>::value << "\n";
return new detail::Impl(*that);
}
#endif
foo.hpp
Code:
#pragma once
namespace detail
{
struct Impl;
}
#ifdef ENABLE_CLONE
detail::Impl* Clone(const detail::Impl* that);
#endif
#include "PImpl.hpp"
struct foo
{
#ifdef FORWARD_CONSTRUCTOR
foo();
#endif
stf::XPimpl<detail::Impl> m;
};
Pimpl.hpp
Code:
#pragma once
#include <utility>
#include <type_traits>
#include <memory>
#include <iostream>
namespace stf
{
namespace detail
{
template<typename T1, typename T2>
struct And: std::false_type
{
using type = std::false_type;
constexpr static bool value = false;
};
template<>
struct And<std::true_type, std::true_type>: std::true_type
{
using type = std::true_type;
constexpr static bool value = true;
};
template<typename T1, typename T2>
using And_t = typename And<T1, T2>::type;
template<typename T>
struct IsIncomplete
{
template<typename T2 = T>
std::false_type static Test(decltype(sizeof(T2))*);
std::true_type static Test(...);
using type = decltype(Test(nullptr));
constexpr static bool value = type::value;
};
template<typename T>
using IsIncomplete_t = typename IsIncomplete<T>::type;
template<typename T>
struct CanClone
{
std::true_type static HasRightReturn(T*);
std::false_type static HasRightReturn(...);
template<typename T2 = T>
auto static IsCallable(int*) -> decltype(HasRightReturn( Clone((const T2*)0xBADC0DE) ));
auto static IsCallable(...) -> std::false_type;
using type = decltype(IsCallable(nullptr));
constexpr static bool value = type::value;
};
template<typename T>
using CanClone_t = typename CanClone<T>::type;
template<typename T>
struct IsCopyable
{
using type = And_t<typename std::is_copy_constructible<T>::type, typename std::is_copy_assignable<T>::type>;
constexpr static bool value = type::value;
};
template<typename T>
using IsCopyable_t = typename IsCopyable<T>::type;
}
template<typename T>
class XPimpl
{
public:
template<typename... Args_t>
XPimpl(Args_t&&... Args)
{
std::cout << "Pimpl::Pimpl: T is incomplete: " << std::boolalpha << detail::IsIncomplete_t<T>::value << "\n";
}
template<typename T2 = T> void Clone(std::true_type, const XPimpl& that) { ::Clone(&*that.m_Impl); }
template<typename T2 = T> void Clone(...) { }
XPimpl(const XPimpl& that)
{
std::cout << "Pimpl::Pimpl(const Pimpl& that): T is incomplete: " << std::boolalpha << detail::IsIncomplete_t<T>::value << "\n";
std::cout << "Pimpl::Pimpl(const Pimpl& that): T is cloneable: " << detail::CanClone_t<T>::value << "\n";
Clone(detail::CanClone_t<T>(), that);
}
~XPimpl()
{
std::cout << "~Pimpl::~Pimpl: T is incomplete: " << std::boolalpha << detail::IsIncomplete_t<T>::value << "\n";
}
protected:
std::unique_ptr<T, void(*)(T*)> m_Impl{ nullptr, [](T*) {} }; /* Disable deletion to avoid compile errors */
};
}
SomaTest.cpp
Code:
// SomaTest.cpp : Defines the entry point for the console application.
//
#include "foo.hpp"
int main()
{
foo test;
foo test2(test);
return 0;
}
It seems I must delay checking T until later in the compilation process (since T is incomplete). This is annoying. Possibly do relevant checks as they are needed (e.g. check if copyable when copying, etc)?