This can be solved by selecting the last row using the ROW_NUMBER windowed partition function.
WITH last_item_price (item_id, price, reverse_order)
AS
(SELECT item_id
, price
, ROW_NUMBER() OVER
(PARTITION BY item_id
ORDER BY date DESC) AS reverse_order
FROM item_price)
SELECT i.name
, p.price
FROM items i
JOIN last_item_price p
ON i.item_id = p.item_id
AND p.reverse_order = 1;
Unless the primary key of item_price is defined as the composite key for item_id and date, there is a risk you will have more than one possible price. For instance, if the price changes three times on a single day, then how do you know which is the correct value? I would recommend using a DATETIME or DATETIME2 field to help pick the correct price in that scenario. Alternatively, you can define the primary key as the composite of the two fields and only allow a single price each day.
Another way to avoid the duplication issue is to add an auto-incrementing identity column. This will not be useful for joins and I still strongly recommend using a composite key for the item_id and date fields, but it is a valid alternative. In that case, you can modify the partition function's ORDER BY clause to:
ORDER BY date DESC, item_price_id DESC
In general, I would not recommend naming a field date. Since SQL Server has a data type called DATE, this can cause issues with linters. In some cases, you may be required to use brackets so the field is [date] to disambiguate your intent. A better name is probably price_date or price_change_date.