Вопрос: Что не даёт на C++ писать кроссплатформенные приложения?

Лучший ответ: Да, как бы, ничего не мешает писать один С++ код для множества платформ. Почти весь мой трудовой стаж связан именно с разработкой кроссплатформенных игр. Я работал с несколькими (самодельными и не очень) движками и имею свое собственное детище, прекрасно и однозначно собирающееся на 5 целевых платформ (Win, Mac, Linux, ios, Android), к которым без труда можно добавить и консоли, и новые платформы.
Нет, вру, не без труда. Попотеть над слоем абстракции придется. Но попотеть придется только над ним, все остальное заведется само, т.к. изначально написано в стандарте C++, без расширений под конкретные компиляторы, и с применением ряда очень важных для кроссплатформенности подходов.
Кроссплатформенность подразумевает решение ряда вопросов, которые и выливаются в слой абстракции над операционной системой. Эти вопросы, зачастую, решать никто не хочет. Несколько раз встречал такое сам и еще мне рассказывали о том, как тот или иной движок сперва был только под конкретную ###, а потом решили портировать на @@@. Оказалось, что компилятор, которым всегда и собирали движок, нашпигован расширениями языка, которые конечно же все пользовали на 100%, и при смене компилятора ни один файл исходников не остался без доброй сотни ошибок. Т.е. переписывать надо было ВСЁ.
Mercury13 хорошо рассказал про Unicode пути к файлам. Drakonoved правильно подметил про разделители путей к файлам. Максим Гришин очень хорошо напомнил про порядок следования байт. Это все и есть часть этого ряда вопросов.
У каждой платформы есть свой API, которого не будет на другой платформе. Но на другой платформе будет свой API, со своими именами и схожей функциональностью. И работу с API надо абстрагировать от универсального кода.
Еще, на одной платформе у тебя может быть разомкнутый главный цикл обработки сообщений (Win), а на другой - замкнутый (Android). Надо подстраиваться. GUI везде разный, надо подстраиваться. Сама структура приложения на одной платформе может быть монолитной, а на другой - композиционной. Графические и звуковые API могут быть и кроссплатформенными, однако простоты использования это им не прибавляет. Инициализация все равно будет платформозависимой.
На самом деле даже в рамках работы на одной платформе надо соблюдать ряд правил, чтобы иметь возможность из одного кода получать и 32-битное приложение, и 64-битное тоже. Об этом неплохо написано на сайте разработчиков PVS-Studio.
И все это решается. От части - с помощью архитектурных приемов. Один из таких я уже показывал в другом своем ответе.
И еще эти вопросы можно не решать.
ДубльГИС, например, уже давно работает на базе Qt, что сильно упростило им кроссплатформенную жизнь. Qt решает ряд проблем кроссплатформенности.

Худший ответ: Си — язык очень близкий к железу. Когда мы пишем на Java, мы пишем код под Java-машину. На Си мы пишем код под процессор. Главная проблема, связанная с любыми подобными языками,— отсутствие понимания целевых ОС программистами и тестирования — тестерами. Это нужно, ибо абстрагирование от машины очень тонкое.
Поскольку перед нами очень тонкая прослойка, абстрагирующая от машины, приходится принимать меры, чтобы код переносился с машины на машину (даже с x86 на x64). Отлично это описано в блоге PVS-Studio.
В Windows и Linux машина-то одна и та же, но конкретно в Си++ к этому добавляется устаревшая по некоторым вещам стандартная библиотека. Примеры…
• Поддержка юникодных имён файлов пока не кроссплатформенная.
• Унаследоваться от std::ostream — ужос такой ещё.
• Как сконвертировать текст в UTF-8? Загугли и посмотри, какой там вавилон из шаблонов.
Отсюда огромное количество производителезависимых расширений, чужих библиотек, повторяющих стандартную, и прочее. Важный пункт тут: чужих библиотек, повторяющих стандартную. Если у тебя строки нестандартные и файлы нестандартные — какая тут кроссплатформенность?