Thread: Persistent compile-time state

  1. #16
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Which means I cannot clone T via normal means, so I'm not sure how to clone the object using a normal clone function. The compiler would just parse the function, see that T is unknown and barf.
    O_o

    You can clone `T' via normal means by separating where the "normal means" lives with respect to the code having an incomplete view. (The compiler would see a `T' that is known in context.) The mechanism I illustrated works with an incomplete as viewed from the perspective of the container allowing the actual utility to be implemented in the same context as the the incomplete would be implemented.

    Anyway, I'll return with a minimal example tomorrow.
    If you want my assistance, you will only post code using standard components.

    A general solution, allowing instantiatation of a template using an incomplete type is rarely useful, is just "SFINAE" over the visible incompleteness of a type so an error could be raised with `static_assert' or similar.
    o_O

    I feel I gave the impression that a check for incompleteness is otherwise useful... which is wrong.

    The short version: only use "SFINAE" over an incomplete type to immediately fail spectacularly with a particular error.

    A "SFINAE" over an incomplete type is never a practical solution to an implementation strategy. A "SFINAE" over an incomplete type does change the behavior according to the completeness of a type theoretically allowing a template component to change behavior according to the visible completeness of a type. Different instantiations of the same expansion of a template using "SFINAE" over an incomplete type may though have different views of completeness. (A type may be complete in one translation unit while incomplete in a different translation unit.) The problem comes when the different instantiations, expanded with different views, are linked together into a binary. The linker process will not see the different instantiations as having different types even if the implementation is different so simply chooses one instantiation to use everywhere. (A template is expanded during compilation within a translation unit during the compiling process. A component with the exact same label in multiple translation units usually violates the one definition rule resulting in a linker error. A few different strategies for linking templates has been used over the years. The most practical and common approach to linking different expansions of the same template comes from allowing the linker to inspect the labels for a naming scheme that informs the linker to be silent about duplicated template expansions.) The compiled code from different translation units as built from template components expanded from different views may thus exhibit different behavior than expected because the code incorrectly assumes the underlying template has behaved differently than the chosen form of the template expansion. The ultimate result of the mechanism when used as an implementation strategy is then to cause silent misbehavior during program execution potentially changing depending on how the program was linked.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  2. #17
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by phantomotap View Post
    You can clone `T' via normal means by separating where the "normal means" lives with respect to the code having an incomplete view. (The compiler would see a `T' that is known in context.) The mechanism I illustrated works with an incomplete as viewed from the perspective of the container allowing the actual utility to be implemented in the same context as the the incomplete would be implemented.
    I get this to mean:

    MyContainer.hpp:
    foo* Clone(const foo*); // Forward declare
    Clone(...);

    foo.cpp:
    Code:
    foo* Clone(const foo* that) { return new foo(that); } // Implement
    Am I getting this right? This would certainly work.

    Quote Originally Posted by phantomotap View Post
    If you want my assistance, you will only post code using standard components.
    What do you mean by "standard components"?

    Quote Originally Posted by phantomotap View Post
    I feel I gave the impression that a check for incompleteness is otherwise useful... which is wrong.

    The short version: only use "SFINAE" over an incomplete type to immediately fail spectacularly with a particular error.
    Okay, I get you. So only do SFINAE over incomplete types to detect incomplete types and fail with an error. Don't try to do fancy magic on incomplete types. At worst, it can violate ODR.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  3. #18
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Am I getting this right? This would certainly work.
    O_o

    You are very nearly the mark. I was intending that the client, here "foo.h", would declare the `Clone' function. You are, otherwise, correct.

    What do you mean by "standard components"?
    That was really a comment for the future.

    The last time you, or "Visual C++", needed help parsing code to find a bug you posted an example using a lot of "Boost" components. I don't keep "Boost" installed, and I haven't used "Boost" now in several years. If you use "Boost", I may not be able to parse the code. (I don't have "Boost" installed so my compiler will certainly fail.) If you stick to components in the C++11 standard, I may be able to help.

    Don't try to do fancy magic on incomplete types. At worst, it can violate ODR.
    Indeed.

    For the sake of experiment, remove any code within the `MyContainer' implementation which operates on a `T'. You should only print "incomplete" or "complete", determined by "SFINAE" over an incomplete type, as appropriate.

    Add a harness, some code for testing, to the example "foo.cpp" client which copies a `foo' object. Compile and run the "foo.cpp" client with a `main' that only knows and only invokes the harness. You should always see the "complete" message because there is only one view.

    Compile the same modified "foo.cpp" code into an object, as in linker, file. Link and run the client with a `main' directly copying a `foo' object. You may see "complete" or "incomplete" because the two translation units have a different view of the completeness of the type.

    You are doing the same thing; you just can't get to the linking stage because the compiler is reusing to build the component.

    If the code actually did compile to something you could link, you'd see different behavior depending on the files are linked.

    You'd have a heisenbug living in the templates.

    Very not fun...

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  4. #19
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    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)?
    Attached Files Attached Files
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  5. #20
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    O_o

    The code seems mostly fine.

    You need to upgrade your "GCC" installation, or you need to use a different form for checking the completeness of a type.

    You are using the global `Clone' within the implementation even if `ENABLE_CLONE' is not defined.

    *shrug*

    I never think of using the approach from the start because support has always been kind of wonky, but you may get away with using "extern template" in your client headers. You would have to document the requirement, but you'd have to document or firewall some requirement in any event.

    Add the line `extern template class stf::XPimpl<detail::Impl>;' to the top of the "foo.hpp" file.

    Add the line `template class stf::XPimpl<detail::Impl>;' below the definition in the "foo.cpp" file.

    The compiler shouldn't expand the used components while parsing "foo.hpp" as included from the "SomaTest.cpp" file.

    I'll refrain from any other comments until you decide about or try using the "extern templates" approach.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  6. #21
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by phantomotap View Post
    You need to upgrade your "GCC" installation, or you need to use a different form for checking the completeness of a type.

    You are using the global `Clone' within the implementation even if `ENABLE_CLONE' is not defined.
    You're right. I forgot to test that (I thought I did, but eh, mistakes happen).
    By changing the lines to:

    Code:
    		void CloneImpl(std::true_type, const T* that)
    		{
    			Clone(that); 
    		}
    
    		void CloneImpl(...) { }
    
    		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";
    			CloneImpl(detail::CanClone_t<T>(), &*that.m_Impl);
    		}
    gcc no longer complains. The problems, apparently, is that using the global namespace operator forces the compiler to do the lookup on the spot (still fuzzy on how lookup works exactly), and since it does not exist, it fails (this is what I can interpret from gcc's error output). The second problem is basically that the function call must depend on some unknown parameter. In this case, it seems that depending on T, which is the primary template type for the class is enough for gcc to delay looking up Clone. Again, this is what I gather from gcc's output.

    I'll try your suggestions tomorrow and report back on that. It would be nice if I could just let the client automatically get the code for cloning, moving and whatever else needs to be delayed due to the incomplete T, like #including a header or inheriting from a base class, or something.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  7. #22
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    So I tried the extern template method, but it didn't really work. It break VC++ in non-intended ways, I think (running it reports that T is not incomplete in various instances, but trying to do sizeof(T) in such a place gives a compile error). Gcc ignores it completely, giving the same results as before.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #23
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by Elysia View Post
    (running it reports that T is not incomplete in various instances, but trying to do sizeof(T) in such a place gives a compile error)
    If VC++ gives a compiler error, then it is doing the right thing. sizeof() is not valid on incomplete types.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  9. #24
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    My point was that

    std::cout << "Pimpl::Pimpl(const Pimpl& that): T is incomplete: " << std::boolalpha << detail::IsIncomplete_t<T>::value << "\n"; gives false, while
    sizeof(T) gives a compile error.

    This seems to give the impression that while compiling the template, T is incomplete, but the actually generated code prints false instead of true, which is weird!
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 2
    Last Post: 01-12-2013, 10:11 AM
  2. Persistent Segmentation Fault
    By Imanuel in forum C Programming
    Replies: 4
    Last Post: 07-26-2010, 08:29 PM
  3. Persistent connections
    By Niara in forum Networking/Device Communication
    Replies: 6
    Last Post: 09-25-2007, 05:27 AM